Cleanup oxide — Part #2 (#13312)

* remove all oxide related code

* Update lightningcss to version 1.24.1

* update tests to match bumped Lightning CSS output

---------

Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
This commit is contained in:
Robin Malfait
2024-03-22 17:12:14 +01:00
committed by GitHub
co-authored by depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
parent 24f2a3faf8
commit 8bb7c599dd
109 changed files with 25441 additions and 28963 deletions
+47 -115
View File
@@ -1,6 +1,5 @@
let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax')
let { env } = require('../../../lib/lib/sharedState')
let {
appendToInputFile,
@@ -72,22 +71,12 @@ describe('static build', () => {
env: { NODE_ENV: 'production' },
})
if (env.ENGINE === 'stable') {
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(css`
.bg-primary {
background-color: black;
}
`)
}
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`)
})
it('can use a tailwind.config.ts configuration file', async () => {
@@ -132,22 +121,12 @@ describe('static build', () => {
env: { NODE_ENV: 'production' },
})
if (env.ENGINE === 'stable') {
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(css`
.bg-primary {
background-color: black;
}
`)
}
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`)
})
})
@@ -188,34 +167,18 @@ describe('watcher', () => {
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`)
})
if (env.ENGINE === 'stable') {
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`)
}
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`)
return runningProcess.stop()
})
@@ -252,34 +215,18 @@ describe('watcher', () => {
await appendToInputFile('glob/index.html', html`<div class="bg-red-500"></div>`)
})
if (env.ENGINE === 'stable') {
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`)
}
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`)
return runningProcess.stop()
})
@@ -411,32 +358,17 @@ describe('watcher', () => {
)
})
if (env.ENGINE === 'stable') {
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(css`
.btn {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
`)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(css`
.btn {
background-color: #ef4444;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
`)
}
expect(await readOutputFile(/index\.\w+\.css$/)).toIncludeCss(css`
.btn {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
`)
return runningProcess.stop()
})
@@ -1,6 +1,5 @@
let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax')
let { env } = require('../../../lib/lib/sharedState')
let { readOutputFile, appendToInputFile, writeInputFile } = require('../../io')({
output: 'dist',
@@ -61,38 +60,20 @@ describe('watcher', () => {
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`)
await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
return runningProcess.stop()
})
@@ -128,38 +109,20 @@ describe('watcher', () => {
await appendToInputFile('glob/index.html', html`<div class="bg-red-500"></div>`)
await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
return runningProcess.stop()
})
@@ -284,40 +247,22 @@ describe('watcher', () => {
)
await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
/* prettier-ignore */
.btn {
border-radius: 0.25rem;
background-color: #ef4444;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
return runningProcess.stop()
})
@@ -1,6 +1,5 @@
let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax')
let { env } = require('../../../lib/lib/sharedState')
let { readOutputFile, appendToInputFile, writeInputFile } = require('../../io')({
output: 'dist',
@@ -61,38 +60,20 @@ describe('watcher', () => {
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`)
await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
return runningProcess.stop()
})
@@ -128,38 +109,20 @@ describe('watcher', () => {
await appendToInputFile('glob/index.html', html`<div class="bg-red-500"></div>`)
await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
return runningProcess.stop()
})
@@ -282,36 +245,19 @@ describe('watcher', () => {
)
await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.btn {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.btn {
background-color: #ef4444;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.btn {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
`
)
return runningProcess.stop()
})
@@ -379,36 +325,19 @@ describe('watcher', () => {
)
await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.btn {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.btn {
background-color: #ef4444;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.btn {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
`
)
return runningProcess.stop()
}, 30000)
+60 -138
View File
@@ -1,6 +1,5 @@
let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax')
let { env } = require('../../../lib/lib/sharedState')
let { readOutputFile, appendToInputFile, writeInputFile, removeFile } = require('../../io')({
output: 'dist',
@@ -62,26 +61,14 @@ describe('static build', () => {
env: { NODE_ENV: 'production' },
})
if (env.ENGINE === 'stable') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-primary {
background-color: black;
}
`
)
}
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`
)
})
it('can use a tailwind.config.ts configuration file', async () => {
@@ -120,26 +107,14 @@ describe('static build', () => {
env: { NODE_ENV: 'production' },
})
if (env.ENGINE === 'stable') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-primary {
background-color: black;
}
`
)
}
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`
)
})
})
@@ -175,38 +150,20 @@ describe('watcher', () => {
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`)
await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
return runningProcess.stop()
})
@@ -242,38 +199,20 @@ describe('watcher', () => {
await appendToInputFile('glob/index.html', html`<div class="bg-red-500"></div>`)
await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
return runningProcess.stop()
})
@@ -396,39 +335,22 @@ describe('watcher', () => {
)
await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
background-color: #ef4444;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
expect(await readOutputFile('index.css')).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
return runningProcess.stop()
})
+53 -133
View File
@@ -2,7 +2,6 @@ let path = require('path')
let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax')
let resolveToolRoot = require('../../resolve-tool-root')
let { env } = require('../../../lib/lib/sharedState')
let version = require('../../../package.json').version
@@ -19,7 +18,6 @@ let {
})
let EXECUTABLE = 'node ../../lib/cli.js'
let testStable = env.ENGINE === 'stable' ? test : test.skip
function dedent(input) {
let lines = input.split('\n')
@@ -98,7 +96,7 @@ describe('Build command', () => {
expect(withoutMinify.length).toBeGreaterThan(withMinify.length)
})
testStable('--no-autoprefixer', async () => {
test('--no-autoprefixer', async () => {
await writeInputFile('index.html', html`<div class="select-none"></div>`)
await $(`${EXECUTABLE} --output ./dist/main.css`)
@@ -184,7 +182,7 @@ describe('Build command', () => {
)
})
testStable('--postcss (postcss.config.js)', async () => {
test('--postcss (postcss.config.js)', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
let customConfig = javascript`
@@ -217,45 +215,25 @@ describe('Build command', () => {
await $(`${EXECUTABLE} --output ./dist/main.css --postcss`)
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold-after {
font-weight: 700;
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold-after {
font-weight: 700;
}
.btn-after {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold-after {
font-weight: 700;
}
.btn-after {
background-color: #ef4444;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
`
)
}
.btn-after {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
`
)
})
testStable('--postcss (custom.postcss.config.js)', async () => {
test('--postcss (custom.postcss.config.js)', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
let customConfig = javascript`
@@ -288,45 +266,25 @@ describe('Build command', () => {
await $(`${EXECUTABLE} --output ./dist/main.css --postcss ./custom.postcss.config.js`)
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold-after {
font-weight: 700;
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold-after {
font-weight: 700;
}
.btn-after {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.font-bold-after {
font-weight: 700;
}
.btn-after {
background-color: #ef4444;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
`
)
}
.btn-after {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
`
)
})
testStable('--postcss supports process options', async () => {
test('--postcss supports process options', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
let customConfig = javascript`
@@ -360,7 +318,7 @@ describe('Build command', () => {
expect(contents).toContain(`/*# sourceMappingURL`)
})
testStable('--postcss supports process options with custom config', async () => {
test('--postcss supports process options with custom config', async () => {
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
let customConfig = javascript`
@@ -481,7 +439,7 @@ describe('Build command', () => {
return runningProcess.stop()
})
testStable('postcss-import is included when using a custom postcss configuration', async () => {
test('postcss-import is included when using a custom postcss configuration', async () => {
cleanupFile('src/test.css')
await writeInputFile('index.html', html`<div class="md:something-cool"></div>`)
@@ -509,28 +467,8 @@ describe('Build command', () => {
test('--help', async () => {
let { combined } = await $(`${EXECUTABLE} --help`)
if (env.ENGINE === 'oxide') {
expect(dedent(combined)).toEqual(
dedent(`
tailwindcss v${version}
Usage:
tailwindcss build [options]
Options:
-i, --input Input file
-o, --output Output file
-w, --watch Watch for changes and rebuild as needed
-p, --poll Use polling instead of filesystem events when watching
--content Content paths to use for removing unused classes
-m, --minify Minify the output
-c, --config Path to a custom config file
-h, --help Display usage information
`)
)
} else if (env.ENGINE === 'stable') {
expect(dedent(combined)).toEqual(
dedent(`
expect(dedent(combined)).toEqual(
dedent(`
tailwindcss v${version}
Usage:
@@ -548,8 +486,7 @@ describe('Build command', () => {
--no-autoprefixer Disable autoprefixer
-h, --help Display usage information
`)
)
}
)
})
})
@@ -611,7 +548,7 @@ describe('Init command', () => {
expect((await readOutputFile('../full.config.js')).split('\n').length).toBeGreaterThan(50)
})
testStable('--postcss', async () => {
test('--postcss', async () => {
expect(await fileExists('postcss.config.js')).toBe(true)
await removeFile('postcss.config.js')
expect(await fileExists('postcss.config.js')).toBe(false)
@@ -631,38 +568,21 @@ describe('Init command', () => {
test('--help', async () => {
let { combined } = await $(`${EXECUTABLE} init --help`)
if (env.ENGINE === 'oxide') {
expect(dedent(combined)).toEqual(
dedent(`
tailwindcss v${version}
expect(dedent(combined)).toEqual(
dedent(`
tailwindcss v${version}
Usage:
tailwindcss init [options]
Usage:
tailwindcss init [options]
Options:
--esm Initialize configuration file as ESM
--ts Initialize configuration file as TypeScript
-f, --full Include the default values for all options in the generated configuration file
-h, --help Display usage information
`)
)
} else if (env.ENGINE === 'stable') {
expect(dedent(combined)).toEqual(
dedent(`
tailwindcss v${version}
Usage:
tailwindcss init [options]
Options:
--esm Initialize configuration file as ESM
--ts Initialize configuration file as TypeScript
-p, --postcss Initialize a \`postcss.config.js\` file
-f, --full Include the default values for all options in the generated configuration file
-h, --help Display usage information
`)
)
}
Options:
--esm Initialize configuration file as ESM
--ts Initialize configuration file as TypeScript
-p, --postcss Initialize a \`postcss.config.js\` file
-f, --full Include the default values for all options in the generated configuration file
-h, --help Display usage information
`)
)
})
test('ESM config is created by default in an ESM project', async () => {
@@ -1,7 +1,6 @@
let fs = require('fs')
let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax')
let { env } = require('../../../lib/lib/sharedState')
let { readOutputFile, appendToInputFile, writeInputFile, removeFile } = require('../../io')({
output: 'dist',
@@ -72,43 +71,23 @@ describe('static build', () => {
env: { NODE_ENV: 'production' },
})
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.bg-red-600 {
--tw-bg-opacity: 1;
background-color: rgb(220 38 38 / var(--tw-bg-opacity));
}
.bg-red-600 {
--tw-bg-opacity: 1;
background-color: rgb(220 38 38 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.bg-red-600 {
background-color: #dc2626;
}
.font-bold {
font-weight: 700;
}
`
)
}
.font-bold {
font-weight: 700;
}
`
)
})
it('can use a tailwind.config.js configuration file with ESM syntax', async () => {
@@ -145,26 +124,14 @@ describe('static build', () => {
env: { NODE_ENV: 'production' },
})
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
background-color: black;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`
)
})
it('can use a tailwind.config.ts configuration file', async () => {
@@ -203,26 +170,14 @@ describe('static build', () => {
env: { NODE_ENV: 'production' },
})
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
background-color: black;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`
)
})
it('can read from a config file from an @config directive', async () => {
@@ -262,26 +217,14 @@ describe('static build', () => {
env: { NODE_ENV: 'production' },
})
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-yellow {
--tw-bg-opacity: 1;
background-color: rgb(255 255 0 / var(--tw-bg-opacity));
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-yellow {
background-color: #ff0;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-yellow {
--tw-bg-opacity: 1;
background-color: rgb(255 255 0 / var(--tw-bg-opacity));
}
`
)
})
it('can read from a config file from an @config directive inside an @import from postcss-import', async () => {
@@ -329,26 +272,14 @@ describe('static build', () => {
env: { NODE_ENV: 'production' },
})
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-yellow {
--tw-bg-opacity: 1;
background-color: rgb(255 255 0 / var(--tw-bg-opacity));
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-yellow {
background-color: #ff0;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-yellow {
--tw-bg-opacity: 1;
background-color: rgb(255 255 0 / var(--tw-bg-opacity));
}
`
)
})
it('should work with raw content', async () => {
@@ -375,26 +306,14 @@ describe('static build', () => {
env: { NODE_ENV: 'production' },
})
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
`
)
})
})
@@ -430,38 +349,20 @@ describe('watcher', () => {
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`)
await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
return runningProcess.stop()
})
@@ -497,38 +398,20 @@ describe('watcher', () => {
await appendToInputFile('glob/index.html', html`<div class="bg-red-500"></div>`)
await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
return runningProcess.stop()
})
@@ -703,42 +586,22 @@ describe('watcher', () => {
)
await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
background-color: #ef4444;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
return runningProcess.stop()
})
@@ -800,26 +663,14 @@ describe('watcher', () => {
let runningProcess = $('node ../../lib/cli.js -i ./src/index.css -o ./dist/main.css -w')
await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-yellow {
--tw-bg-opacity: 1;
background-color: rgb(255 255 0 / var(--tw-bg-opacity));
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-yellow {
background-color: #ff0;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-yellow {
--tw-bg-opacity: 1;
background-color: rgb(255 255 0 / var(--tw-bg-opacity));
}
`
)
await writeInputFile(
'index.css',
@@ -832,26 +683,14 @@ describe('watcher', () => {
)
await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-yellow {
--tw-bg-opacity: 1;
background-color: rgb(255 255 119 / var(--tw-bg-opacity));
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-yellow {
background-color: #ff7;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-yellow {
--tw-bg-opacity: 1;
background-color: rgb(255 255 119 / var(--tw-bg-opacity));
}
`
)
await writeInputFile(
'tailwind.2.config.js',
@@ -876,26 +715,14 @@ describe('watcher', () => {
)
await runningProcess.onStderr(ready)
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-yellow {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-yellow {
background-color: #fff;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-yellow {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
`
)
return runningProcess.stop()
})
+60 -141
View File
@@ -2,7 +2,6 @@ require('isomorphic-fetch')
let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax')
let { env } = require('../../../lib/lib/sharedState')
let { readOutputFile, appendToInputFile, writeInputFile, removeFile } = require('../../io')({
output: 'dist',
@@ -75,26 +74,14 @@ describe('static build', () => {
env: { NODE_ENV: 'production', NO_COLOR: '1' },
})
if (env.ENGINE === 'stable') {
expect(await readOutputFile(/index.[a-z0-9_-]+\.css$/i)).toIncludeCss(
css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile(/index.[a-z0-9_-]+\.css$/i)).toIncludeCss(
css`
.bg-primary {
background-color: black;
}
`
)
}
expect(await readOutputFile(/index.[a-z0-9_-]+\.css$/i)).toIncludeCss(
css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`
)
})
it('can use a tailwind.config.ts configuration file', async () => {
@@ -131,26 +118,14 @@ describe('static build', () => {
env: { NODE_ENV: 'production', NO_COLOR: '1' },
})
if (env.ENGINE === 'stable') {
expect(await readOutputFile(/index.[a-z0-9_-]+\.css$/i)).toIncludeCss(
css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile(/index.[a-z0-9_-]+\.css$/i)).toIncludeCss(
css`
.bg-primary {
background-color: black;
}
`
)
}
expect(await readOutputFile(/index.[a-z0-9_-]+\.css$/i)).toIncludeCss(
css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`
)
})
})
@@ -194,38 +169,20 @@ describe('watcher', () => {
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`)
await runningProcess.onStdout((message) => message.includes('page reload'))
if (env.ENGINE === 'stable') {
expect(await fetchCSS()).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await fetchCSS()).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
expect(await fetchCSS()).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
return runningProcess.stop()
})
@@ -265,38 +222,20 @@ describe('watcher', () => {
await appendToInputFile('glob/index.html', html`<div class="bg-red-500"></div>`)
await runningProcess.onStdout((message) => message.includes('page reload'))
if (env.ENGINE === 'stable') {
expect(await fetchCSS()).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await fetchCSS()).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
expect(await fetchCSS()).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
return runningProcess.stop()
})
@@ -435,42 +374,22 @@ describe('watcher', () => {
)
await runningProcess.onStdout((message) => message.includes('hmr update /index.css'))
if (env.ENGINE === 'stable') {
expect(await fetchCSS()).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await fetchCSS()).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
background-color: #ef4444;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
expect(await fetchCSS()).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
return runningProcess.stop()
})
+60 -138
View File
@@ -1,6 +1,5 @@
let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax')
let { env } = require('../../../lib/lib/sharedState')
let {
appendToInputFile,
@@ -58,26 +57,14 @@ describe('static build', () => {
await $('webpack --mode=production')
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
background-color: black;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`
)
})
it('can use a tailwind.config.ts configuration file', async () => {
@@ -114,26 +101,14 @@ describe('static build', () => {
await $('webpack --mode=production')
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
background-color: black;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`
)
})
})
@@ -172,38 +147,20 @@ describe('watcher', () => {
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`)
})
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
return runningProcess.stop()
})
@@ -242,38 +199,20 @@ describe('watcher', () => {
await appendToInputFile('glob/index.html', html`<div class="bg-red-500"></div>`)
})
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
return runningProcess.stop()
})
@@ -401,39 +340,22 @@ describe('watcher', () => {
)
})
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.btn {
background-color: #ef4444;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
return runningProcess.stop()
})
+75 -173
View File
@@ -1,6 +1,5 @@
let $ = require('../../execute')
let { css, html, javascript } = require('../../syntax')
let { env } = require('../../../lib/lib/sharedState')
let {
appendToInputFile,
@@ -58,26 +57,14 @@ describe('static build', () => {
await $('webpack --mode=production')
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
background-color: black;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`
)
})
it('can use a tailwind.config.ts configuration file', async () => {
@@ -114,26 +101,14 @@ describe('static build', () => {
await $('webpack --mode=production')
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
background-color: black;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`
)
})
})
@@ -172,38 +147,20 @@ describe('watcher', () => {
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`)
})
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
return runningProcess.stop()
})
@@ -242,38 +199,20 @@ describe('watcher', () => {
await appendToInputFile('index.html', html`<div class="bg-red-500"></div>`)
})
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
.font-normal {
font-weight: 400;
}
`
)
return runningProcess.stop()
})
@@ -401,39 +340,22 @@ describe('watcher', () => {
)
})
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.btn {
background-color: #ef4444;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
}
.font-bold {
font-weight: 700;
}
`
)
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.btn {
border-radius: 0.25rem;
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.font-bold {
font-weight: 700;
}
`
)
return runningProcess.stop()
})
@@ -464,43 +386,23 @@ describe('watcher', () => {
await waitForOutputFileCreation('main.css')
if (env.ENGINE === 'stable') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
.bg-red-600 {
--tw-bg-opacity: 1;
background-color: rgb(220 38 38 / var(--tw-bg-opacity));
}
.bg-red-600 {
--tw-bg-opacity: 1;
background-color: rgb(220 38 38 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
`
)
}
if (env.ENGINE === 'oxide') {
expect(await readOutputFile('main.css')).toIncludeCss(
css`
.bg-red-500 {
background-color: #ef4444;
}
.bg-red-600 {
background-color: #dc2626;
}
.font-bold {
font-weight: 700;
}
`
)
}
.font-bold {
font-weight: 700;
}
`
)
return runningProcess.stop()
})
+100 -108
View File
@@ -51,7 +51,7 @@
"eslint-plugin-prettier": "^4.2.1",
"jest": "^29.6.0",
"jest-diff": "^29.6.0",
"lightningcss": "1.18.0",
"lightningcss": "1.24.1",
"prettier": "^2.8.8",
"rimraf": "^5.0.0",
"source-map-js": "^1.0.2",
@@ -5990,9 +5990,9 @@
}
},
"node_modules/lightningcss": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.18.0.tgz",
"integrity": "sha512-uk10tNxi5fhZqU93vtYiQgx/8a9f0Kvtj5AXIm+VlOXY+t/DWDmCZWJEkZJmmALgvbS6aAW8or+Kq85eJ6TDTw==",
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.24.1.tgz",
"integrity": "sha512-kUpHOLiH5GB0ERSv4pxqlL0RYKnOXtgGtVe7shDGfhS0AZ4D1ouKFYAcLcZhql8aMspDNzaUCumGHZ78tb2fTg==",
"dev": true,
"dependencies": {
"detect-libc": "^1.0.3"
@@ -6005,20 +6005,21 @@
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"lightningcss-darwin-arm64": "1.18.0",
"lightningcss-darwin-x64": "1.18.0",
"lightningcss-linux-arm-gnueabihf": "1.18.0",
"lightningcss-linux-arm64-gnu": "1.18.0",
"lightningcss-linux-arm64-musl": "1.18.0",
"lightningcss-linux-x64-gnu": "1.18.0",
"lightningcss-linux-x64-musl": "1.18.0",
"lightningcss-win32-x64-msvc": "1.18.0"
"lightningcss-darwin-arm64": "1.24.1",
"lightningcss-darwin-x64": "1.24.1",
"lightningcss-freebsd-x64": "1.24.1",
"lightningcss-linux-arm-gnueabihf": "1.24.1",
"lightningcss-linux-arm64-gnu": "1.24.1",
"lightningcss-linux-arm64-musl": "1.24.1",
"lightningcss-linux-x64-gnu": "1.24.1",
"lightningcss-linux-x64-musl": "1.24.1",
"lightningcss-win32-x64-msvc": "1.24.1"
}
},
"node_modules/lightningcss-darwin-arm64": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.18.0.tgz",
"integrity": "sha512-OqjydwtiNPgdH1ByIjA1YzqvDG/OMR6L3LPN6wRl1729LB0y4Mik7L06kmZaTb+pvUHr+NmDd2KCwnlrQ4zO3w==",
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.24.1.tgz",
"integrity": "sha512-1jQ12jBy+AE/73uGQWGSafK5GoWgmSiIQOGhSEXiFJSZxzV+OXIx+a9h2EYHxdJfX864M+2TAxWPWb0Vv+8y4w==",
"cpu": [
"arm64"
],
@@ -6036,9 +6037,9 @@
}
},
"node_modules/lightningcss-darwin-x64": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.18.0.tgz",
"integrity": "sha512-mNiuPHj89/JHZmJMp+5H8EZSt6EL5DZRWJ31O6k3DrLLnRIQjXuXdDdN8kP7LoIkeWI5xvyD60CsReJm+YWYAw==",
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.24.1.tgz",
"integrity": "sha512-R4R1d7VVdq2mG4igMU+Di8GPf0b64ZLnYVkubYnGG0Qxq1KaXQtAzcLI43EkpnoWvB/kUg8JKCWH4S13NfiLcQ==",
"cpu": [
"x64"
],
@@ -6055,10 +6056,30 @@
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-freebsd-x64": {
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.24.1.tgz",
"integrity": "sha512-z6NberUUw5ALES6Ixn2shmjRRrM1cmEn1ZQPiM5IrZ6xHHL5a1lPin9pRv+w6eWfcrEo+qGG6R9XfJrpuY3e4g==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm-gnueabihf": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.18.0.tgz",
"integrity": "sha512-S+25JjI6601HiAVoTDXW6SqH+E94a+FHA7WQqseyNHunOgVWKcAkNEc2LJvVxgwTq6z41sDIb9/M3Z9wa9lk4A==",
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.24.1.tgz",
"integrity": "sha512-NLQLnBQW/0sSg74qLNI8F8QKQXkNg4/ukSTa+XhtkO7v3BnK19TS1MfCbDHt+TTdSgNEBv0tubRuapcKho2EWw==",
"cpu": [
"arm"
],
@@ -6076,9 +6097,9 @@
}
},
"node_modules/lightningcss-linux-arm64-gnu": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.18.0.tgz",
"integrity": "sha512-JSqh4+21dCgBecIQUet35dtE4PhhSEMyqe3y0ZNQrAJQ5kyUPSQHiw81WXnPJcOSTTpG0TyMLiC8K//+BsFGQA==",
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.24.1.tgz",
"integrity": "sha512-AQxWU8c9E9JAjAi4Qw9CvX2tDIPjgzCTrZCSXKELfs4mCwzxRkHh2RCxX8sFK19RyJoJAjA/Kw8+LMNRHS5qEg==",
"cpu": [
"arm64"
],
@@ -6096,9 +6117,9 @@
}
},
"node_modules/lightningcss-linux-arm64-musl": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.18.0.tgz",
"integrity": "sha512-2FWHa8iUhShnZnqhn2wfIcK5adJat9hAAaX7etNsoXJymlliDIOFuBQEsba2KBAZSM4QqfQtvRdR7m8i0I7ybQ==",
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.24.1.tgz",
"integrity": "sha512-JCgH/SrNrhqsguUA0uJUM1PvN5+dVuzPIlXcoWDHSv2OU/BWlj2dUYr3XNzEw748SmNZPfl2NjQrAdzaPOn1lA==",
"cpu": [
"arm64"
],
@@ -6116,9 +6137,9 @@
}
},
"node_modules/lightningcss-linux-x64-gnu": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.18.0.tgz",
"integrity": "sha512-plCPGQJtDZHcLVKVRLnQVF2XRsIC32WvuJhQ7fJ7F6BV98b/VZX0OlX05qUaOESD9dCDHjYSfxsgcvOKgCWh7A==",
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.24.1.tgz",
"integrity": "sha512-TYdEsC63bHV0h47aNRGN3RiK7aIeco3/keN4NkoSQ5T8xk09KHuBdySltWAvKLgT8JvR+ayzq8ZHnL1wKWY0rw==",
"cpu": [
"x64"
],
@@ -6136,9 +6157,9 @@
}
},
"node_modules/lightningcss-linux-x64-musl": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.18.0.tgz",
"integrity": "sha512-na+BGtVU6fpZvOHKhnlA0XHeibkT3/46nj6vLluG3kzdJYoBKU6dIl7DSOk++8jv4ybZyFJ0aOFMMSc8g2h58A==",
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.24.1.tgz",
"integrity": "sha512-HLfzVik3RToot6pQ2Rgc3JhfZkGi01hFetHt40HrUMoeKitLoqUUT5owM6yTZPTytTUW9ukLBJ1pc3XNMSvlLw==",
"cpu": [
"x64"
],
@@ -6156,9 +6177,9 @@
}
},
"node_modules/lightningcss-win32-x64-msvc": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.18.0.tgz",
"integrity": "sha512-5qeAH4RMNy2yMNEl7e5TI6upt/7xD2ZpHWH4RkT8iJ7/6POS5mjHbXWUO9Q1hhDhqkdzGa76uAdMzEouIeCyNw==",
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.24.1.tgz",
"integrity": "sha512-joEupPjYJ7PjZtDsS5lzALtlAudAbgIBMGJPNeFe5HfdmJXFd13ECmEM+5rXNxYVMRHua2w8132R6ab5Z6K9Ow==",
"cpu": [
"x64"
],
@@ -8502,43 +8523,6 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"oxide": {
"name": "tailwindcss-oxide",
"version": "0.1.0",
"extraneous": true,
"license": "MIT",
"workspaces": [
"node"
]
},
"oxide-node-api-shim": {
"name": "@tailwindcss/oxide-shim",
"extraneous": true,
"license": "MIT"
},
"oxide/crates/node": {
"name": "@tailwindcss/oxide",
"version": "0.0.0",
"extraneous": true,
"license": "MIT",
"devDependencies": {
"@napi-rs/cli": "^2.14.3"
},
"engines": {
"node": ">= 10"
},
"optionalDependencies": {
"@tailwindcss/oxide-darwin-arm64": "0.0.0",
"@tailwindcss/oxide-darwin-x64": "0.0.0",
"@tailwindcss/oxide-freebsd-x64": "0.0.0",
"@tailwindcss/oxide-linux-arm-gnueabihf": "0.0.0",
"@tailwindcss/oxide-linux-arm64-gnu": "0.0.0",
"@tailwindcss/oxide-linux-arm64-musl": "0.0.0",
"@tailwindcss/oxide-linux-x64-gnu": "0.0.0",
"@tailwindcss/oxide-linux-x64-musl": "0.0.0",
"@tailwindcss/oxide-win32-x64-msvc": "0.0.0"
}
}
},
"dependencies": {
@@ -12740,75 +12724,83 @@
}
},
"lightningcss": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.18.0.tgz",
"integrity": "sha512-uk10tNxi5fhZqU93vtYiQgx/8a9f0Kvtj5AXIm+VlOXY+t/DWDmCZWJEkZJmmALgvbS6aAW8or+Kq85eJ6TDTw==",
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.24.1.tgz",
"integrity": "sha512-kUpHOLiH5GB0ERSv4pxqlL0RYKnOXtgGtVe7shDGfhS0AZ4D1ouKFYAcLcZhql8aMspDNzaUCumGHZ78tb2fTg==",
"dev": true,
"requires": {
"detect-libc": "^1.0.3",
"lightningcss-darwin-arm64": "1.18.0",
"lightningcss-darwin-x64": "1.18.0",
"lightningcss-linux-arm-gnueabihf": "1.18.0",
"lightningcss-linux-arm64-gnu": "1.18.0",
"lightningcss-linux-arm64-musl": "1.18.0",
"lightningcss-linux-x64-gnu": "1.18.0",
"lightningcss-linux-x64-musl": "1.18.0",
"lightningcss-win32-x64-msvc": "1.18.0"
"lightningcss-darwin-arm64": "1.24.1",
"lightningcss-darwin-x64": "1.24.1",
"lightningcss-freebsd-x64": "1.24.1",
"lightningcss-linux-arm-gnueabihf": "1.24.1",
"lightningcss-linux-arm64-gnu": "1.24.1",
"lightningcss-linux-arm64-musl": "1.24.1",
"lightningcss-linux-x64-gnu": "1.24.1",
"lightningcss-linux-x64-musl": "1.24.1",
"lightningcss-win32-x64-msvc": "1.24.1"
}
},
"lightningcss-darwin-arm64": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.18.0.tgz",
"integrity": "sha512-OqjydwtiNPgdH1ByIjA1YzqvDG/OMR6L3LPN6wRl1729LB0y4Mik7L06kmZaTb+pvUHr+NmDd2KCwnlrQ4zO3w==",
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.24.1.tgz",
"integrity": "sha512-1jQ12jBy+AE/73uGQWGSafK5GoWgmSiIQOGhSEXiFJSZxzV+OXIx+a9h2EYHxdJfX864M+2TAxWPWb0Vv+8y4w==",
"dev": true,
"optional": true
},
"lightningcss-darwin-x64": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.18.0.tgz",
"integrity": "sha512-mNiuPHj89/JHZmJMp+5H8EZSt6EL5DZRWJ31O6k3DrLLnRIQjXuXdDdN8kP7LoIkeWI5xvyD60CsReJm+YWYAw==",
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.24.1.tgz",
"integrity": "sha512-R4R1d7VVdq2mG4igMU+Di8GPf0b64ZLnYVkubYnGG0Qxq1KaXQtAzcLI43EkpnoWvB/kUg8JKCWH4S13NfiLcQ==",
"dev": true,
"optional": true
},
"lightningcss-freebsd-x64": {
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.24.1.tgz",
"integrity": "sha512-z6NberUUw5ALES6Ixn2shmjRRrM1cmEn1ZQPiM5IrZ6xHHL5a1lPin9pRv+w6eWfcrEo+qGG6R9XfJrpuY3e4g==",
"dev": true,
"optional": true
},
"lightningcss-linux-arm-gnueabihf": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.18.0.tgz",
"integrity": "sha512-S+25JjI6601HiAVoTDXW6SqH+E94a+FHA7WQqseyNHunOgVWKcAkNEc2LJvVxgwTq6z41sDIb9/M3Z9wa9lk4A==",
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.24.1.tgz",
"integrity": "sha512-NLQLnBQW/0sSg74qLNI8F8QKQXkNg4/ukSTa+XhtkO7v3BnK19TS1MfCbDHt+TTdSgNEBv0tubRuapcKho2EWw==",
"dev": true,
"optional": true
},
"lightningcss-linux-arm64-gnu": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.18.0.tgz",
"integrity": "sha512-JSqh4+21dCgBecIQUet35dtE4PhhSEMyqe3y0ZNQrAJQ5kyUPSQHiw81WXnPJcOSTTpG0TyMLiC8K//+BsFGQA==",
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.24.1.tgz",
"integrity": "sha512-AQxWU8c9E9JAjAi4Qw9CvX2tDIPjgzCTrZCSXKELfs4mCwzxRkHh2RCxX8sFK19RyJoJAjA/Kw8+LMNRHS5qEg==",
"dev": true,
"optional": true
},
"lightningcss-linux-arm64-musl": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.18.0.tgz",
"integrity": "sha512-2FWHa8iUhShnZnqhn2wfIcK5adJat9hAAaX7etNsoXJymlliDIOFuBQEsba2KBAZSM4QqfQtvRdR7m8i0I7ybQ==",
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.24.1.tgz",
"integrity": "sha512-JCgH/SrNrhqsguUA0uJUM1PvN5+dVuzPIlXcoWDHSv2OU/BWlj2dUYr3XNzEw748SmNZPfl2NjQrAdzaPOn1lA==",
"dev": true,
"optional": true
},
"lightningcss-linux-x64-gnu": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.18.0.tgz",
"integrity": "sha512-plCPGQJtDZHcLVKVRLnQVF2XRsIC32WvuJhQ7fJ7F6BV98b/VZX0OlX05qUaOESD9dCDHjYSfxsgcvOKgCWh7A==",
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.24.1.tgz",
"integrity": "sha512-TYdEsC63bHV0h47aNRGN3RiK7aIeco3/keN4NkoSQ5T8xk09KHuBdySltWAvKLgT8JvR+ayzq8ZHnL1wKWY0rw==",
"dev": true,
"optional": true
},
"lightningcss-linux-x64-musl": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.18.0.tgz",
"integrity": "sha512-na+BGtVU6fpZvOHKhnlA0XHeibkT3/46nj6vLluG3kzdJYoBKU6dIl7DSOk++8jv4ybZyFJ0aOFMMSc8g2h58A==",
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.24.1.tgz",
"integrity": "sha512-HLfzVik3RToot6pQ2Rgc3JhfZkGi01hFetHt40HrUMoeKitLoqUUT5owM6yTZPTytTUW9ukLBJ1pc3XNMSvlLw==",
"dev": true,
"optional": true
},
"lightningcss-win32-x64-msvc": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.18.0.tgz",
"integrity": "sha512-5qeAH4RMNy2yMNEl7e5TI6upt/7xD2ZpHWH4RkT8iJ7/6POS5mjHbXWUO9Q1hhDhqkdzGa76uAdMzEouIeCyNw==",
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.24.1.tgz",
"integrity": "sha512-joEupPjYJ7PjZtDsS5lzALtlAudAbgIBMGJPNeFe5HfdmJXFd13ECmEM+5rXNxYVMRHua2w8132R6ab5Z6K9Ow==",
"dev": true,
"optional": true
},
+2 -5
View File
@@ -12,12 +12,9 @@
"tailwind": "lib/cli.js",
"tailwindcss": "lib/cli.js"
},
"tailwindcss": {
"engine": "stable"
},
"scripts": {
"prebuild": "npm run generate && rimraf lib",
"build": "swc src --out-dir lib --copy-files --config jsc.transform.optimizer.globals.vars.__OXIDE__='\"false\"'",
"build": "swc src --out-dir lib --copy-files",
"postbuild": "esbuild lib/cli-peer-dependencies.js --bundle --platform=node --outfile=peers/index.js --define:process.env.CSS_TRANSFORMER_WASM=false",
"rebuild-fixtures": "npm run build && node -r @swc/register scripts/rebuildFixtures.js",
"style": "eslint .",
@@ -60,7 +57,7 @@
"eslint-plugin-prettier": "^4.2.1",
"jest": "^29.6.0",
"jest-diff": "^29.6.0",
"lightningcss": "1.18.0",
"lightningcss": "1.24.1",
"prettier": "^2.8.8",
"rimraf": "^5.0.0",
"source-map-js": "^1.0.2",
+1 -5
View File
@@ -1,7 +1,3 @@
#!/usr/bin/env node
if (__OXIDE__) {
module.exports = require('./oxide/cli')
} else {
module.exports = require('./cli/index')
}
module.exports = require('./cli/index')
+4 -11
View File
@@ -185,17 +185,10 @@ let state = {
let files = fastGlob.sync(this.contentPatterns.all)
for (let file of files) {
if (__OXIDE__) {
content.push({
file,
extension: path.extname(file).slice(1),
})
} else {
content.push({
content: fs.readFileSync(path.resolve(file), 'utf8'),
extension: path.extname(file).slice(1),
})
}
content.push({
content: fs.readFileSync(path.resolve(file), 'utf8'),
extension: path.extname(file).slice(1),
})
}
// Resolve raw content in the tailwind config
-21
View File
@@ -1340,16 +1340,6 @@ export let corePlugins = {
'space-x': (value) => {
value = value === '0' ? '0px' : value
if (__OXIDE__) {
return {
'& > :not([hidden]) ~ :not([hidden])': {
'--tw-space-x-reverse': '0',
'margin-inline-end': `calc(${value} * var(--tw-space-x-reverse))`,
'margin-inline-start': `calc(${value} * calc(1 - var(--tw-space-x-reverse)))`,
},
}
}
return {
'& > :not([hidden]) ~ :not([hidden])': {
'--tw-space-x-reverse': '0',
@@ -1385,17 +1375,6 @@ export let corePlugins = {
'divide-x': (value) => {
value = value === '0' ? '0px' : value
if (__OXIDE__) {
return {
'& > :not([hidden]) ~ :not([hidden])': {
'@defaults border-width': {},
'--tw-divide-x-reverse': '0',
'border-inline-end-width': `calc(${value} * var(--tw-divide-x-reverse))`,
'border-inline-start-width': `calc(${value} * calc(1 - var(--tw-divide-x-reverse)))`,
},
}
}
return {
'& > :not([hidden]) ~ :not([hidden])': {
'@defaults border-width': {},
+2 -6
View File
@@ -4,12 +4,8 @@ import log from './util/log'
let defaults = {
optimizeUniversalDefaults: false,
generalizedModifiers: true,
get disableColorOpacityUtilitiesByDefault() {
return __OXIDE__
},
get relativeContentPathsByDefault() {
return __OXIDE__
},
disableColorOpacityUtilitiesByDefault: false,
relativeContentPathsByDefault: false,
}
let featureFlags = {
+23 -41
View File
@@ -130,41 +130,25 @@ export default function expandTailwindAtRules(context) {
env.DEBUG && console.time('Reading changed files')
if (__OXIDE__) {
// TODO: Pass through or implement `extractor`
for (let candidate of require('@tailwindcss/oxide').parseCandidateStringsFromFiles(
context.changedContent
// Object.assign({}, builtInTransformers, context.tailwindConfig.content.transform)
)) {
candidates.add(candidate)
}
/** @type {[item: {file?: string, content?: string}, meta: {transformer: any, extractor: any}][]} */
let regexParserContent = []
// for (let { file, content, extension } of context.changedContent) {
// let transformer = getTransformer(context.tailwindConfig, extension)
// let extractor = getExtractor(context, extension)
// getClassCandidatesOxide(file, transformer(content), extractor, candidates, seen)
// }
} else {
/** @type {[item: {file?: string, content?: string}, meta: {transformer: any, extractor: any}][]} */
let regexParserContent = []
for (let item of context.changedContent) {
let transformer = getTransformer(context.tailwindConfig, item.extension)
let extractor = getExtractor(context, item.extension)
regexParserContent.push([item, { transformer, extractor }])
}
for (let item of context.changedContent) {
let transformer = getTransformer(context.tailwindConfig, item.extension)
let extractor = getExtractor(context, item.extension)
regexParserContent.push([item, { transformer, extractor }])
}
const BATCH_SIZE = 500
const BATCH_SIZE = 500
for (let i = 0; i < regexParserContent.length; i += BATCH_SIZE) {
let batch = regexParserContent.slice(i, i + BATCH_SIZE)
await Promise.all(
batch.map(async ([{ file, content }, { transformer, extractor }]) => {
content = file ? await fs.promises.readFile(file, 'utf8') : content
getClassCandidates(transformer(content), extractor, candidates, seen)
})
)
}
for (let i = 0; i < regexParserContent.length; i += BATCH_SIZE) {
let batch = regexParserContent.slice(i, i + BATCH_SIZE)
await Promise.all(
batch.map(async ([{ file, content }, { transformer, extractor }]) => {
content = file ? await fs.promises.readFile(file, 'utf8') : content
getClassCandidates(transformer(content), extractor, candidates, seen)
})
)
}
env.DEBUG && console.timeEnd('Reading changed files')
@@ -176,15 +160,13 @@ export default function expandTailwindAtRules(context) {
env.DEBUG && console.time('Generate rules')
env.DEBUG && console.time('Sorting candidates')
let sortedCandidates = __OXIDE__
? candidates
: new Set(
[...candidates].sort((a, z) => {
if (a === z) return 0
if (a < z) return -1
return 1
})
)
let sortedCandidates = new Set(
[...candidates].sort((a, z) => {
if (a === z) return 0
if (a < z) return -1
return 1
})
)
env.DEBUG && console.timeEnd('Sorting candidates')
generateRules(sortedCandidates, context)
env.DEBUG && console.timeEnd('Generate rules')
-4
View File
@@ -1,16 +1,12 @@
import pkg from '../../package.json'
export const env =
typeof process !== 'undefined'
? {
NODE_ENV: process.env.NODE_ENV,
DEBUG: resolveDebug(process.env.DEBUG),
ENGINE: pkg.tailwindcss.engine,
}
: {
NODE_ENV: 'production',
DEBUG: false,
ENGINE: pkg.tailwindcss.engine,
}
export const contextMap = new Map()
-60
View File
@@ -34,66 +34,6 @@ module.exports = function tailwindcss(configOrPath) {
await processTailwindFeatures(context)(root, result)
},
__OXIDE__ &&
function lightningCssPlugin(_root, result) {
let postcss = require('postcss')
let lightningcss = require('lightningcss')
let browserslist = require('browserslist')
try {
let transformed = lightningcss.transform({
filename: result.opts.from,
code: Buffer.from(result.root.toString()),
minify: false,
sourceMap: !!result.map,
inputSourceMap: result.map ? result.map.toString() : undefined,
targets:
typeof process !== 'undefined' && process.env.JEST_WORKER_ID
? { chrome: 106 << 16 }
: lightningcss.browserslistToTargets(
browserslist(require('../package.json').browserslist)
),
drafts: {
nesting: true,
customMedia: true,
},
})
result.map = Object.assign(result.map ?? {}, {
toJSON() {
return transformed.map.toJSON()
},
toString() {
return transformed.map.toString()
},
})
result.root = postcss.parse(transformed.code.toString('utf8'))
} catch (err) {
if (typeof process !== 'undefined' && process.env.JEST_WORKER_ID) {
let lines = err.source.split('\n')
err = new Error(
[
'Error formatting using Lightning CSS:',
'',
...[
'```css',
...lines.slice(Math.max(err.loc.line - 3, 0), err.loc.line),
' '.repeat(err.loc.column - 1) + '^-- ' + err.toString(),
...lines.slice(err.loc.line, err.loc.line + 2),
'```',
],
].join('\n')
)
}
if (Error.captureStackTrace) {
Error.captureStackTrace(err, lightningCssPlugin)
}
throw err
}
},
env.DEBUG &&
function (root) {
console.timeEnd('JIT TOTAL')
+1 -4
View File
@@ -28,12 +28,10 @@
"dev": true,
"license": "MIT",
"workspaces": [
"integrations/*",
"oxide/crates/node"
"integrations/*"
],
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"@tailwindcss/oxide": "file:oxide/crates/node",
"arg": "^5.0.2",
"browserslist": "^4.21.5",
"chokidar": "^3.5.3",
@@ -9849,7 +9847,6 @@
"@swc/core": "^1.3.55",
"@swc/jest": "^0.2.26",
"@swc/register": "^0.1.10",
"@tailwindcss/oxide": "file:oxide/crates/node",
"arg": "^5.0.2",
"autoprefixer": "^10.4.14",
"browserslist": "^4.21.5",
+225 -227
View File
@@ -1,279 +1,277 @@
import { crosscheck, run, html, css } from './util/run'
import { run, html, css } from './util/run'
crosscheck(() => {
test('basic', () => {
let config = {
content: [
{
raw: html`
<div class="animate-spin"></div>
<div class="hover:animate-ping"></div>
<div class="group-hover:animate-bounce"></div>
`,
},
],
}
test('basic', () => {
let config = {
content: [
{
raw: html`
<div class="animate-spin"></div>
<div class="hover:animate-ping"></div>
<div class="group-hover:animate-bounce"></div>
`,
},
],
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@keyframes spin {
to {
transform: rotate(360deg);
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@keyframes spin {
to {
transform: rotate(360deg);
}
.animate-spin {
animation: 1s linear infinite spin;
}
.animate-spin {
animation: 1s linear infinite spin;
}
@keyframes ping {
75%,
100% {
opacity: 0;
transform: scale(2);
}
@keyframes ping {
75%,
100% {
opacity: 0;
transform: scale(2);
}
}
.hover\:animate-ping:hover {
animation: 1s cubic-bezier(0, 0, 0.2, 1) infinite ping;
}
@keyframes bounce {
0%,
100% {
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
transform: translateY(-25%);
}
.hover\:animate-ping:hover {
animation: 1s cubic-bezier(0, 0, 0.2, 1) infinite ping;
50% {
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
transform: none;
}
@keyframes bounce {
0%,
100% {
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
transform: translateY(-25%);
}
50% {
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
transform: none;
}
}
.group:hover .group-hover\:animate-bounce {
animation: 1s infinite bounce;
}
`)
})
}
.group:hover .group-hover\:animate-bounce {
animation: 1s infinite bounce;
}
`)
})
})
test('custom', () => {
let config = {
content: [{ raw: html`<div class="animate-one"></div>` }],
theme: {
extend: {
keyframes: {
one: { to: { transform: 'rotate(360deg)' } },
},
animation: {
one: 'one 2s',
},
test('custom', () => {
let config = {
content: [{ raw: html`<div class="animate-one"></div>` }],
theme: {
extend: {
keyframes: {
one: { to: { transform: 'rotate(360deg)' } },
},
animation: {
one: 'one 2s',
},
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@keyframes one {
to {
transform: rotate(360deg);
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@keyframes one {
to {
transform: rotate(360deg);
}
.animate-one {
animation: 2s one;
}
`)
})
}
.animate-one {
animation: 2s one;
}
`)
})
})
test('custom prefixed', () => {
let config = {
prefix: 'tw-',
content: [{ raw: `<div class="tw-animate-one"></div>` }],
theme: {
extend: {
keyframes: {
one: { to: { transform: 'rotate(360deg)' } },
},
animation: {
one: 'one 2s',
},
test('custom prefixed', () => {
let config = {
prefix: 'tw-',
content: [{ raw: `<div class="tw-animate-one"></div>` }],
theme: {
extend: {
keyframes: {
one: { to: { transform: 'rotate(360deg)' } },
},
animation: {
one: 'one 2s',
},
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@keyframes tw-one {
to {
transform: rotate(360deg);
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@keyframes tw-one {
to {
transform: rotate(360deg);
}
.tw-animate-one {
animation: 2s tw-one;
}
`)
})
}
.tw-animate-one {
animation: 2s tw-one;
}
`)
})
})
test('multiple', () => {
let config = {
content: [{ raw: html`<div class="animate-multiple"></div>` }],
theme: {
extend: {
animation: {
multiple: 'bounce 2s linear, pulse 3s ease-in',
},
test('multiple', () => {
let config = {
content: [{ raw: html`<div class="animate-multiple"></div>` }],
theme: {
extend: {
animation: {
multiple: 'bounce 2s linear, pulse 3s ease-in',
},
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@keyframes bounce {
0%,
100% {
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
transform: translateY(-25%);
}
50% {
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
transform: none;
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@keyframes bounce {
0%,
100% {
animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
transform: translateY(-25%);
}
@keyframes pulse {
50% {
opacity: 0.5;
}
50% {
animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
transform: none;
}
.animate-multiple {
animation: 2s linear bounce, 3s ease-in pulse;
}
@keyframes pulse {
50% {
opacity: 0.5;
}
`)
})
}
.animate-multiple {
animation: 2s linear bounce, 3s ease-in pulse;
}
`)
})
})
test('multiple custom', () => {
let config = {
content: [{ raw: html`<div class="animate-multiple"></div>` }],
theme: {
extend: {
keyframes: {
one: { to: { transform: 'rotate(360deg)' } },
two: { to: { transform: 'scale(1.23)' } },
},
animation: {
multiple: 'one 2s, two 3s',
},
test('multiple custom', () => {
let config = {
content: [{ raw: html`<div class="animate-multiple"></div>` }],
theme: {
extend: {
keyframes: {
one: { to: { transform: 'rotate(360deg)' } },
two: { to: { transform: 'scale(1.23)' } },
},
animation: {
multiple: 'one 2s, two 3s',
},
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@keyframes one {
to {
transform: rotate(360deg);
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@keyframes one {
to {
transform: rotate(360deg);
}
@keyframes two {
to {
transform: scale(1.23);
}
}
@keyframes two {
to {
transform: scale(1.23);
}
.animate-multiple {
animation: 2s one, 3s two;
}
`)
})
}
.animate-multiple {
animation: 2s one, 3s two;
}
`)
})
})
test('with dots in the name', () => {
let config = {
content: [
{
raw: html`
<div class="animate-zoom-.5"></div>
<div class="animate-zoom-1.5"></div>
`,
test('with dots in the name', () => {
let config = {
content: [
{
raw: html`
<div class="animate-zoom-.5"></div>
<div class="animate-zoom-1.5"></div>
`,
},
],
theme: {
extend: {
keyframes: {
'zoom-.5': { to: { transform: 'scale(0.5)' } },
'zoom-1.5': { to: { transform: 'scale(1.5)' } },
},
],
theme: {
extend: {
keyframes: {
'zoom-.5': { to: { transform: 'scale(0.5)' } },
'zoom-1.5': { to: { transform: 'scale(1.5)' } },
},
animation: {
'zoom-.5': 'zoom-.5 2s',
'zoom-1.5': 'zoom-1.5 2s',
},
animation: {
'zoom-.5': 'zoom-.5 2s',
'zoom-1.5': 'zoom-1.5 2s',
},
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@keyframes zoom-\.5 {
to {
transform: scale(0.5);
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@keyframes zoom-\.5 {
to {
transform: scale(0.5);
}
.animate-zoom-\.5 {
animation: 2s zoom-\.5;
}
.animate-zoom-\.5 {
animation: 2s zoom-\.5;
}
@keyframes zoom-1\.5 {
to {
transform: scale(1.5);
}
@keyframes zoom-1\.5 {
to {
transform: scale(1.5);
}
}
.animate-zoom-1\.5 {
animation: 2s zoom-1\.5;
}
`)
})
}
.animate-zoom-1\.5 {
animation: 2s zoom-1\.5;
}
`)
})
})
test('with dots in the name and prefix', () => {
let config = {
prefix: 'tw-',
content: [
{
raw: html`
<div class="tw-animate-zoom-.5"></div>
<div class="tw-animate-zoom-1.5"></div>
`,
test('with dots in the name and prefix', () => {
let config = {
prefix: 'tw-',
content: [
{
raw: html`
<div class="tw-animate-zoom-.5"></div>
<div class="tw-animate-zoom-1.5"></div>
`,
},
],
theme: {
extend: {
keyframes: {
'zoom-.5': { to: { transform: 'scale(0.5)' } },
'zoom-1.5': { to: { transform: 'scale(1.5)' } },
},
],
theme: {
extend: {
keyframes: {
'zoom-.5': { to: { transform: 'scale(0.5)' } },
'zoom-1.5': { to: { transform: 'scale(1.5)' } },
},
animation: {
'zoom-.5': 'zoom-.5 2s',
'zoom-1.5': 'zoom-1.5 2s',
},
animation: {
'zoom-.5': 'zoom-.5 2s',
'zoom-1.5': 'zoom-1.5 2s',
},
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@keyframes tw-zoom-\.5 {
to {
transform: scale(0.5);
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@keyframes tw-zoom-\.5 {
to {
transform: scale(0.5);
}
.tw-animate-zoom-\.5 {
animation: 2s tw-zoom-\.5;
}
.tw-animate-zoom-\.5 {
animation: 2s tw-zoom-\.5;
}
@keyframes tw-zoom-1\.5 {
to {
transform: scale(1.5);
}
@keyframes tw-zoom-1\.5 {
to {
transform: scale(1.5);
}
}
.tw-animate-zoom-1\.5 {
animation: 2s tw-zoom-1\.5;
}
`)
})
}
.tw-animate-zoom-1\.5 {
animation: 2s tw-zoom-1\.5;
}
`)
})
})
+751 -1302
View File
File diff suppressed because it is too large Load Diff
+2102 -2482
View File
File diff suppressed because it is too large Load Diff
+433 -437
View File
@@ -1,460 +1,456 @@
import { crosscheck, run, html, css, defaults } from './util/run'
import { run, html, css, defaults } from './util/run'
crosscheck(() => {
test('basic arbitrary properties', () => {
let config = {
content: [
{
raw: html`<div class="[paint-order:markers]"></div>`,
},
],
corePlugins: { preflight: false },
}
test('basic arbitrary properties', () => {
let config = {
content: [
{
raw: html`<div class="[paint-order:markers]"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.\[paint-order\:markers\] {
paint-order: markers;
}
`)
})
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.\[paint-order\:markers\] {
paint-order: markers;
}
`)
})
test('different arbitrary properties are picked up separately', () => {
let config = {
content: [
{
raw: html`<div class="[foo:bar] [bar:baz]"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.\[bar\:baz\] {
bar: baz;
}
.\[foo\:bar\] {
foo: bar;
}
`)
})
})
test('different arbitrary properties are picked up separately', () => {
let config = {
content: [
{
raw: html`<div class="[foo:bar] [bar:baz]"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.\[bar\:baz\] {
bar: baz;
}
.\[foo\:bar\] {
foo: bar;
}
`)
})
test('arbitrary properties with modifiers', () => {
let config = {
content: [
{
raw: html`<div class="dark:lg:hover:[paint-order:markers]"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
@media (prefers-color-scheme: dark) {
@media (min-width: 1024px) {
.dark\:lg\:hover\:\[paint-order\:markers\]:hover {
paint-order: markers;
}
})
test('arbitrary properties with modifiers', () => {
let config = {
content: [
{
raw: html`<div class="dark:lg:hover:[paint-order:markers]"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
@media (prefers-color-scheme: dark) {
@media (min-width: 1024px) {
.dark\:lg\:hover\:\[paint-order\:markers\]:hover {
paint-order: markers;
}
}
`)
})
}
`)
})
test('arbitrary properties are sorted after utilities', () => {
let config = {
content: [
{
raw: html`<div
class="content-none [paint-order:markers] hover:pointer-events-none"
></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.content-none {
--tw-content: none;
content: var(--tw-content);
}
.\[paint-order\:markers\] {
paint-order: markers;
}
.hover\:pointer-events-none:hover {
pointer-events: none;
}
`)
})
})
test('arbitrary properties are sorted after utilities', () => {
let config = {
content: [
{
raw: html`<div class="content-none [paint-order:markers] hover:pointer-events-none"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.content-none {
--tw-content: none;
content: var(--tw-content);
}
.\[paint-order\:markers\] {
paint-order: markers;
}
.hover\:pointer-events-none:hover {
pointer-events: none;
}
`)
})
test('using CSS variables', () => {
let config = {
content: [
{
raw: html`<div class="[--my-var:auto]"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.\[--my-var\:auto\] {
--my-var: auto;
}
`)
})
})
test('using CSS variables', () => {
let config = {
content: [
{
raw: html`<div class="[--my-var:auto]"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.\[--my-var\:auto\] {
--my-var: auto;
}
`)
})
test('using underscores as spaces', () => {
let config = {
content: [
{
raw: html`<div class="[--my-var:2px_4px]"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.\[--my-var\:2px_4px\] {
--my-var: 2px 4px;
}
`)
})
})
test('using underscores as spaces', () => {
let config = {
content: [
{
raw: html`<div class="[--my-var:2px_4px]"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.\[--my-var\:2px_4px\] {
--my-var: 2px 4px;
}
`)
})
test('using the important modifier', () => {
let config = {
content: [
{
raw: html`<div class="![--my-var:2px_4px]"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.\!\[--my-var\:2px_4px\] {
--my-var: 2px 4px !important;
}
`)
})
})
test('using the important modifier', () => {
let config = {
content: [
{
raw: html`<div class="![--my-var:2px_4px]"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.\!\[--my-var\:2px_4px\] {
--my-var: 2px 4px !important;
}
`)
})
test('colons are allowed in quotes', () => {
let config = {
content: [
{
raw: html`<div class="[content:'foo:bar']"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.\[content\:\'foo\:bar\'\] {
content: 'foo:bar';
}
`)
})
})
test('colons are allowed in quotes', () => {
let config = {
content: [
{
raw: html`<div class="[content:'foo:bar']"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.\[content\:\'foo\:bar\'\] {
content: 'foo:bar';
}
`)
})
test('colons are allowed in braces', () => {
let config = {
content: [
{
raw: html`<div class="[background-image:url(http://example.com/picture.jpg)]"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.\[background-image\:url\(http\:\/\/example\.com\/picture\.jpg\)\] {
background-image: url('http://example.com/picture.jpg');
}
`)
})
})
test('colons are allowed in braces', () => {
let config = {
content: [
{
raw: html`<div class="[background-image:url(http://example.com/picture.jpg)]"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.\[background-image\:url\(http\:\/\/example\.com\/picture\.jpg\)\] {
background-image: url('http://example.com/picture.jpg');
}
`)
})
test('invalid class', () => {
let config = {
content: [
{
raw: html`<div class="[a:b:c:d]"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
`)
})
})
test('invalid class', () => {
let config = {
content: [
{
raw: html`<div class="[a:b:c:d]"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
`)
})
test('invalid arbitrary property', () => {
let config = {
content: [
{
raw: html`<div class="[autoplay:\${autoplay}]"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
`)
})
})
test('invalid arbitrary property', () => {
let config = {
content: [
{
raw: html`<div class="[autoplay:\${autoplay}]"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
`)
})
test('invalid arbitrary property 2', () => {
let config = {
content: [
{
raw: html`[0:02]`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
`)
})
})
test('invalid arbitrary property 2', () => {
let config = {
content: [
{
raw: html`[0:02]`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
`)
})
test('using fractional spacing values inside theme() function', () => {
let config = {
content: [
{
raw: html`<div
class="[border:_calc(5vw_-_theme(spacing[2.5]))_double_theme('colors.fuchsia.700')]"
></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.\[border\:_calc\(5vw_-_theme\(spacing\[2\.5\]\)\)_double_theme\(\'colors\.fuchsia\.700\'\)\] {
border: calc(5vw - 0.625rem) double #a21caf;
}
`)
})
})
test('using fractional spacing values inside theme() function', () => {
let config = {
content: [
{
raw: html`<div
class="[border:_calc(5vw_-_theme(spacing[2.5]))_double_theme('colors.fuchsia.700')]"
></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.\[border\:_calc\(5vw_-_theme\(spacing\[2\.5\]\)\)_double_theme\(\'colors\.fuchsia\.700\'\)\] {
border: calc(5vw - 0.625rem) double #a21caf;
}
`)
})
test('using multiple arbitrary props having fractional spacing values', () => {
let config = {
content: [
{
raw: html`<div
class="[height:_calc(100vh_-_theme(spacing[2.5]))] [box-shadow:_0_calc(theme(spacing[0.5])_*_-1)_theme(colors.red.400)_inset]"
></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
${defaults}
.\[box-shadow\:_0_calc\(theme\(spacing\[0\.5\]\)_\*_-1\)_theme\(colors\.red\.400\)_inset\] {
box-shadow: inset 0 -0.125rem #f87171;
}
.\[height\:_calc\(100vh_-_theme\(spacing\[2\.5\]\)\)\] {
height: calc(100vh - 0.625rem);
}
`)
})
})
test('using multiple arbitrary props having fractional spacing values', () => {
let config = {
content: [
{
raw: html`<div
class="[height:_calc(100vh_-_theme(spacing[2.5]))] [box-shadow:_0_calc(theme(spacing[0.5])_*_-1)_theme(colors.red.400)_inset]"
></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
${defaults}
.\[box-shadow\:_0_calc\(theme\(spacing\[0\.5\]\)_\*_-1\)_theme\(colors\.red\.400\)_inset\] {
box-shadow: inset 0 -0.125rem #f87171;
}
.\[height\:_calc\(100vh_-_theme\(spacing\[2\.5\]\)\)\] {
height: calc(100vh - 0.625rem);
}
`)
})
it('should be possible to read theme values in arbitrary properties (without quotes)', () => {
let config = {
content: [{ raw: html`<div class="[--a:theme(colors.blue.500)] [color:var(--a)]"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
${defaults}
.\[--a\:theme\(colors\.blue\.500\)\] {
--a: #3b82f6;
}
.\[color\:var\(--a\)\] {
color: var(--a);
}
`)
})
})
it('should be possible to read theme values in arbitrary properties (without quotes)', () => {
let config = {
content: [{ raw: html`<div class="[--a:theme(colors.blue.500)] [color:var(--a)]"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
${defaults}
.\[--a\:theme\(colors\.blue\.500\)\] {
--a: #3b82f6;
}
.\[color\:var\(--a\)\] {
color: var(--a);
}
`)
})
it('should be possible to read theme values in arbitrary properties (with quotes)', () => {
let config = {
content: [{ raw: html`<div class="[color:var(--a)] [--a:theme('colors.blue.500')]"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
${defaults}
.\[--a\:theme\(\'colors\.blue\.500\'\)\] {
--a: #3b82f6;
}
.\[color\:var\(--a\)\] {
color: var(--a);
}
`)
})
})
it('should be possible to read theme values in arbitrary properties (with quotes)', () => {
let config = {
content: [{ raw: html`<div class="[color:var(--a)] [--a:theme('colors.blue.500')]"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
${defaults}
.\[--a\:theme\(\'colors\.blue\.500\'\)\] {
--a: #3b82f6;
}
.\[color\:var\(--a\)\] {
color: var(--a);
}
`)
})
it('should not generate invalid CSS', () => {
let config = {
content: [
{
raw: html`
<div class="[https://en.wikipedia.org/wiki]"></div>
<div class="[http://example.org]"></div>
<div class="[http://example]"></div>
<div class="[ftp://example]"></div>
<div class="[stillworks:/example]"></div>
`,
// NOTE: In this case `stillworks:/example` being generated is not ideal
// but it at least doesn't produce invalid CSS when run through prettier
// So we can let it through since it is technically valid
},
],
corePlugins: { preflight: false },
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
.\[stillworks\:\/example\] {
stillworks: /example;
}
`)
})
})
it('should not generate invalid CSS', () => {
let config = {
content: [
{
raw: html`
<div class="[https://en.wikipedia.org/wiki]"></div>
<div class="[http://example.org]"></div>
<div class="[http://example]"></div>
<div class="[ftp://example]"></div>
<div class="[stillworks:/example]"></div>
`,
// NOTE: In this case `stillworks:/example` being generated is not ideal
// but it at least doesn't produce invalid CSS when run through prettier
// So we can let it through since it is technically valid
},
],
corePlugins: { preflight: false },
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
.\[stillworks\:\/example\] {
stillworks: /example;
}
`)
})
})
+114 -84
View File
@@ -97,11 +97,11 @@
.mt-\[7px\] {
margin-top: 7px;
}
.mt-\[clamp\(30px\,100px\)\] {
.mt-\[clamp\(30px\2c 100px\)\] {
margin-top: clamp(30px, 100px);
}
.aspect-\[16\/9\] {
aspect-ratio: 16 / 9;
aspect-ratio: 16/9;
}
.aspect-\[var\(--aspect\)\] {
aspect-ratio: var(--aspect);
@@ -134,13 +134,13 @@
min-height: var(--height);
}
.w-\[\'\)\(\)\'\] {
width: ')()';
width: ")()";
}
.w-\[\'\]\[\]\'\] {
width: '][]';
width: "][]";
}
.w-\[\'\}\{\}\'\] {
width: '}{}';
width: "}{}";
}
.w-\[\(\(\)\)\] {
width: (());
@@ -164,22 +164,26 @@
width: calc(100% + 1rem);
}
.w-\[calc\(100\%\/3-1rem\*2\)\] {
width: calc(33.3333% - 2rem);
width: calc(100% / 3 - 1rem * 2);
}
.w-\[calc\(var\(--10-10px\,calc\(-20px-\(-30px--40px\)\)\)-50px\)\] {
.w-\[calc\(var\(--10-10px\2c calc\(-20px-\(-30px--40px\)\)\)-50px\)\] {
width: calc(var(--10-10px, calc(-20px - (-30px - -40px))) - 50px);
}
.w-\[var\(--width\)\] {
width: var(--width);
}
.w-\[var\(--width\,calc\(100\%\+1rem\)\)\] {
.w-\[var\(--width\2c calc\(100\%\+1rem\)\)\] {
width: var(--width, calc(100% + 1rem));
}
.w-\[\{\{\}\}\] {
width: {{}}
width: {
{
}
}
}
.w-\[\{\}\] {
width: {}
width: {
}
}
.min-w-\[3\.23rem\] {
min-width: 3.23rem;
@@ -211,11 +215,15 @@
.flex-\[var\(--flex\)\] {
flex: var(--flex);
}
.flex-shrink-\[var\(--shrink\)\],
.flex-shrink-\[var\(--shrink\)\] {
flex-shrink: var(--shrink);
}
.shrink-\[var\(--shrink\)\] {
flex-shrink: var(--shrink);
}
.flex-grow-\[var\(--grow\)\],
.flex-grow-\[var\(--grow\)\] {
flex-grow: var(--grow);
}
.grow-\[var\(--grow\)\] {
flex-grow: var(--grow);
}
@@ -256,7 +264,7 @@
scaleY(var(--tw-scale-y));
}
.rotate-\[2\.3rad\] {
--tw-rotate: 131.78deg;
--tw-rotate: 2.3rad;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
scaleY(var(--tw-scale-y));
@@ -335,8 +343,8 @@
skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
scaleY(var(--tw-scale-y));
}
.animate-\[pong_1s_cubic-bezier\(0\,0\,0\.2\,1\)_infinite\] {
animation: 1s cubic-bezier(0, 0, 0.2, 1) infinite pong;
.animate-\[pong_1s_cubic-bezier\(0\2c 0\2c 0\.2\2c 1\)_infinite\] {
animation: pong 1s cubic-bezier(0, 0, 0.2, 1) infinite;
}
.animate-\[var\(--value\)\] {
animation: var(--value);
@@ -344,11 +352,11 @@
.cursor-\[pointer\] {
cursor: pointer;
}
.cursor-\[url\(\'\.\/path_to_hand\.cur\'\)_2_2\,pointer\] {
cursor: url('./path_to_hand.cur') 2 2, pointer;
.cursor-\[url\(\'\.\/path_to_hand\.cur\'\)_2_2\2c pointer\] {
cursor: url("./path_to_hand.cur") 2 2, pointer;
}
.cursor-\[url\(hand\.cur\)_2_2\,pointer\] {
cursor: url('hand.cur') 2 2, pointer;
.cursor-\[url\(hand\.cur\)_2_2\2c pointer\] {
cursor: url(hand.cur) 2 2, pointer;
}
.cursor-\[var\(--value\)\] {
cursor: var(--value);
@@ -406,13 +414,13 @@
scroll-padding-top: var(--scroll-padding);
}
.list-\[\'\\1f44d\'\] {
list-style-type: '👍';
list-style-type: "\1f44d";
}
.list-\[var\(--value\)\] {
list-style-type: var(--value);
}
.list-image-\[url\(\.\/my-image\.png\)\] {
list-style-image: url('./my-image.png');
list-style-image: url(./my-image.png);
}
.list-image-\[var\(--value\)\] {
list-style-image: var(--value);
@@ -423,19 +431,19 @@
.columns-\[var\(--columns\)\] {
columns: var(--columns);
}
.auto-cols-\[minmax\(10px\,auto\)\] {
.auto-cols-\[minmax\(10px\2c auto\)\] {
grid-auto-columns: minmax(10px, auto);
}
.auto-rows-\[minmax\(10px\,auto\)\] {
.auto-rows-\[minmax\(10px\2c auto\)\] {
grid-auto-rows: minmax(10px, auto);
}
.grid-cols-\[200px\,repeat\(auto-fill\,minmax\(15\%\,100px\)\)\,300px\] {
.grid-cols-\[200px\2c repeat\(auto-fill\2c minmax\(15\%\2c 100px\)\)\2c 300px\] {
grid-template-columns: 200px repeat(auto-fill, minmax(15%, 100px)) 300px;
}
.grid-cols-\[\[linename\]\,1fr\,auto\] {
.grid-cols-\[\[linename\]\2c 1fr\2c auto\] {
grid-template-columns: [linename] 1fr auto;
}
.grid-rows-\[200px\,repeat\(auto-fill\,minmax\(15\%\,100px\)\)\,300px\] {
.grid-rows-\[200px\2c repeat\(auto-fill\2c minmax\(15\%\2c 100px\)\)\2c 300px\] {
grid-template-rows: 200px repeat(auto-fill, minmax(15%, 100px)) 300px;
}
.gap-\[20px\] {
@@ -578,7 +586,7 @@
border-color: var(--value);
}
.border-\[red_black\] {
border-color: red #000;
border-color: red black;
}
.border-b-\[\#f00\] {
--tw-border-opacity: 1;
@@ -615,7 +623,7 @@
--tw-border-opacity: var(--value);
}
.bg-\[\#0000ffcc\] {
background-color: #00fc;
background-color: #0000ffcc;
}
.bg-\[\#0f0\] {
--tw-bg-opacity: 1;
@@ -631,30 +639,35 @@
.bg-\[color\:var\(--value1\)_var\(--value2\)\] {
background-color: var(--value1) var(--value2);
}
.bg-\[hsl\(0\,100\%\,50\%\)\],
.bg-\[hsl\(0rad\,100\%\,50\%\)\] {
.bg-\[hsl\(0\2c 100\%\2c 50\%\)\] {
--tw-bg-opacity: 1;
background-color: hsl(0 100% 50% / var(--tw-bg-opacity));
}
.bg-\[hsla\(0\,100\%\,50\%\,0\.3\)\],
.bg-\[hsla\(0turn\,100\%\,50\%\,0\.3\)\] {
background-color: #ff00004d;
.bg-\[hsl\(0rad\2c 100\%\2c 50\%\)\] {
--tw-bg-opacity: 1;
background-color: hsl(0rad 100% 50% / var(--tw-bg-opacity));
}
.bg-\[rgb\(123\,123\,123\)\] {
.bg-\[hsla\(0\2c 100\%\2c 50\%\2c 0\.3\)\] {
background-color: hsla(0, 100%, 50%, 0.3);
}
.bg-\[hsla\(0turn\2c 100\%\2c 50\%\2c 0\.3\)\] {
background-color: hsla(0turn, 100%, 50%, 0.3);
}
.bg-\[rgb\(123\2c 123\2c 123\)\] {
--tw-bg-opacity: 1;
background-color: rgb(123 123 123 / var(--tw-bg-opacity));
}
.bg-\[rgb\(123\,_456\,_123\)_black\] {
background-color: #7bff7b black;
.bg-\[rgb\(123\2c _456\2c _123\)_black\] {
background-color: rgb(123, 456, 123) black;
}
.bg-\[rgb\(123_456_789\)\] {
--tw-bg-opacity: 1;
background-color: rgb(123 255 255 / var(--tw-bg-opacity));
background-color: rgb(123 456 789 / var(--tw-bg-opacity));
}
.bg-\[rgba\(123\,123\,123\,0\.5\)\] {
background-color: #7b7b7b80;
.bg-\[rgba\(123\2c 123\2c 123\2c 0\.5\)\] {
background-color: rgba(123, 123, 123, 0.5);
}
.bg-\[var\(--value\)\,var\(--value\)\] {
.bg-\[var\(--value\)\2c var\(--value\)\] {
background-color: var(--value), var(--value);
}
.bg-\[var\(--value1\)_var\(--value2\)\] {
@@ -666,47 +679,53 @@
.bg-opacity-\[var\(--value\)\] {
--tw-bg-opacity: var(--value);
}
.bg-\[image\(\)\,var\(--value\)\] {
.bg-\[image\(\)\2c var\(--value\)\] {
background-image: image(), var(--value);
}
.bg-\[image\:var\(--value\)\,var\(--value\)\] {
.bg-\[image\:var\(--value\)\2c var\(--value\)\] {
background-image: var(--value), var(--value);
}
.bg-\[linear-gradient\(\#eee\,\#fff\)\,conic-gradient\(red\,orange\,yellow\,green\,blue\)\] {
background-image: linear-gradient(#eee, #fff), conic-gradient(red, orange, #ff0, green, #00f);
.bg-\[linear-gradient\(\#eee\2c
\#fff\)\2c
conic-gradient\(red\2c
orange\2c
yellow\2c
green\2c
blue\)\] {
background-image: linear-gradient(#eee, #fff), conic-gradient(red, orange, yellow, green, blue);
}
.bg-\[linear-gradient\(\#eee\,\#fff\)\] {
.bg-\[linear-gradient\(\#eee\2c \#fff\)\] {
background-image: linear-gradient(#eee, #fff);
}
.bg-\[linear-gradient\(to_left\,rgb\(var\(--green\)\)\,blue\)\] {
.bg-\[linear-gradient\(to_left\2c rgb\(var\(--green\)\)\2c blue\)\] {
background-image: linear-gradient(to left, rgb(var(--green)), blue);
}
.bg-\[repeating-conic-gradient\(\#F8F9FA_0\%_25\%\,_white_0\%_50\%\)\] {
.bg-\[repeating-conic-gradient\(\#F8F9FA_0\%_25\%\2c _white_0\%_50\%\)\] {
background-image: repeating-conic-gradient(#f8f9fa 0% 25%, white 0% 50%);
}
.bg-\[url\(\'\/path-to-image\.png\'\)\] {
background-image: url('/path-to-image.png');
background-image: url("/path-to-image.png");
}
.bg-\[url\:var\(--url\)\] {
background-image: var(--url);
}
.from-\[\#da5b66\] {
--tw-gradient-from: #da5b66 var(--tw-gradient-from-position);
--tw-gradient-to: #da5b6600 var(--tw-gradient-to-position);
--tw-gradient-to: rgb(218 91 102 / 0) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.from-\[var\(--color\)\] {
--tw-gradient-from: var(--color) var(--tw-gradient-from-position);
--tw-gradient-to: #fff0 var(--tw-gradient-to-position);
--tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.via-\[\#da5b66\] {
--tw-gradient-to: #da5b6600 var(--tw-gradient-to-position);
--tw-gradient-to: rgb(218 91 102 / 0) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), #da5b66 var(--tw-gradient-via-position),
var(--tw-gradient-to);
}
.via-\[var\(--color\)\] {
--tw-gradient-to: #fff0 var(--tw-gradient-to-position);
--tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--color) var(--tw-gradient-via-position),
var(--tw-gradient-to);
}
@@ -723,7 +742,7 @@
background-size: var(--value);
}
.bg-\[center_top_1rem\] {
background-position: 50% 1rem;
background-position: center top 1rem;
}
.bg-\[position\:200px_100px\] {
background-position: 200px 100px;
@@ -735,7 +754,7 @@
fill: #da5b66;
}
.fill-\[url\(\#icon-gradient\)\] {
fill: url('#icon-gradient');
fill: url(#icon-gradient);
}
.fill-\[var\(--value\)\] {
fill: var(--value);
@@ -747,7 +766,7 @@
stroke: var(--value);
}
.stroke-\[url\(\#icon-gradient\)\] {
stroke: url('#icon-gradient');
stroke: url(#icon-gradient);
}
.stroke-\[20px\] {
stroke-width: 20px;
@@ -755,10 +774,10 @@
.stroke-\[length\:var\(--value\)\] {
stroke-width: var(--value);
}
.object-\[50\%\,50\%\] {
.object-\[50\%\2c 50\%\] {
object-position: 50% 50%;
}
.object-\[top\,right\] {
.object-\[top\2c right\] {
object-position: top right;
}
.object-\[var\(--position\)\] {
@@ -787,7 +806,7 @@
.pt-\[7px\] {
padding-top: 7px;
}
.pt-\[clamp\(30px\,100px\)\] {
.pt-\[clamp\(30px\2c 100px\)\] {
padding-top: clamp(30px, 100px);
}
.indent-\[50\%\] {
@@ -800,27 +819,27 @@
vertical-align: 10em;
}
.font-\[\'Gill_Sans\'\] {
font-family: Gill Sans;
font-family: "Gill Sans";
}
.font-\[\'Some_Font\'\,\'Some_Other_Font\'\] {
font-family: Some Font, Some Other Font;
.font-\[\'Some_Font\'\2c \'Some_Other_Font\'\] {
font-family: "Some Font", "Some Other Font";
}
.font-\[\'Some_Font\'\,sans-serif\] {
font-family: Some Font, sans-serif;
.font-\[\'Some_Font\'\2c sans-serif\] {
font-family: "Some Font", sans-serif;
}
.font-\[\'Some_Font\'\,var\(--other-font\)\] {
font-family: 'Some Font', var(--other-font);
.font-\[\'Some_Font\'\2c var\(--other-font\)\] {
font-family: "Some Font", var(--other-font);
}
.font-\[Georgia\,serif\] {
.font-\[Georgia\2c serif\] {
font-family: Georgia, serif;
}
.font-\[family-name\:var\(--value\)\] {
font-family: var(--value);
}
.font-\[sans-serif\,serif\] {
.font-\[sans-serif\2c serif\] {
font-family: sans-serif, serif;
}
.font-\[serif\,var\(--value\)\] {
.font-\[serif\2c var\(--value\)\] {
font-family: serif, var(--value);
}
.text-\[0\] {
@@ -835,7 +854,7 @@
.text-\[length\:var\(--font-size\)\] {
font-size: var(--font-size);
}
.text-\[min\(10vh\,100px\)\] {
.text-\[min\(10vh\2c 100px\)\] {
font-size: min(10vh, 100px);
}
.font-\[300\] {
@@ -860,8 +879,14 @@
.text-\[color\:var\(--color\)\] {
color: var(--color);
}
.text-\[rgb\(123\,123\,123\)\],
.text-\[rgb\(123\,_123\,_123\)\],
.text-\[rgb\(123\2c 123\2c 123\)\] {
--tw-text-opacity: 1;
color: rgb(123 123 123 / var(--tw-text-opacity));
}
.text-\[rgb\(123\2c _123\2c _123\)\] {
--tw-text-opacity: 1;
color: rgb(123 123 123 / var(--tw-text-opacity));
}
.text-\[rgb\(123_123_123\)\] {
--tw-text-opacity: 1;
color: rgb(123 123 123 / var(--tw-text-opacity));
@@ -873,15 +898,19 @@
--tw-text-opacity: var(--value);
}
.decoration-\[black\] {
text-decoration-color: #000;
text-decoration-color: black;
}
.decoration-\[color\:var\(--color\)\] {
text-decoration-color: var(--color);
}
.decoration-\[rgb\(123\,123\,123\)\],
.decoration-\[rgb\(123\,_123\,_123\)\],
.decoration-\[rgb\(123\2c 123\2c 123\)\] {
text-decoration-color: rgb(123, 123, 123);
}
.decoration-\[rgb\(123\2c _123\2c _123\)\] {
text-decoration-color: rgb(123, 123, 123);
}
.decoration-\[rgb\(123_123_123\)\] {
text-decoration-color: #7b7b7b;
text-decoration-color: rgb(123 123 123);
}
.decoration-\[length\:10px\] {
text-decoration-thickness: 10px;
@@ -896,7 +925,7 @@
--tw-placeholder-opacity: var(--placeholder-opacity);
}
.caret-\[black\] {
caret-color: #000;
caret-color: black;
}
.caret-\[var\(--value\)\] {
caret-color: var(--value);
@@ -935,7 +964,7 @@
outline-offset: 10px;
}
.outline-\[black\] {
outline-color: #000;
outline-color: black;
}
.outline-\[color\:var\(--outline\)\] {
outline-color: var(--outline);
@@ -1049,7 +1078,7 @@
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-hue-rotate-\[1\.57rad\] {
--tw-backdrop-hue-rotate: hue-rotate(89.9544deg);
--tw-backdrop-hue-rotate: hue-rotate(1.57rad);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
@@ -1078,10 +1107,10 @@
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.transition-\[opacity\,width\] {
.transition-\[opacity\2c width\] {
transition-property: opacity, width;
transition-duration: 0.15s;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.delay-\[var\(--delay\)\] {
transition-delay: var(--delay);
@@ -1092,18 +1121,18 @@
.duration-\[var\(--app-duration\)\] {
transition-duration: var(--app-duration);
}
.will-change-\[top\,left\] {
.will-change-\[top\2c left\] {
will-change: top, left;
}
.will-change-\[var\(--will-change\)\] {
will-change: var(--will-change);
}
.content-\[\'\>\'\] {
--tw-content: '>';
--tw-content: ">";
content: var(--tw-content);
}
.content-\[\'hello\'\] {
--tw-content: 'hello';
--tw-content: "hello";
content: var(--tw-content);
}
.content-\[attr\(content-before\)\] {
@@ -1111,7 +1140,8 @@
content: var(--tw-content);
}
@media (min-width: 1024px) {
.lg\:grid-cols-\[200px\,repeat\(auto-fill\,minmax\(15\%\,100px\)\)\,300px\] {
.lg\:grid-cols-\[200px\2c repeat\(auto-fill\2c minmax\(15\%\2c 100px\)\)\2c 300px\] {
grid-template-columns: 200px repeat(auto-fill, minmax(15%, 100px)) 300px;
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+932 -999
View File
File diff suppressed because it is too large Load Diff
+86 -105
View File
@@ -1,126 +1,107 @@
import { crosscheck, run, html, css } from './util/run'
import { run, html, css } from './util/run'
crosscheck(({ stable, oxide }) => {
it('can block classes matched literally', () => {
let config = {
content: [
{
raw: html`<div
class="font-bold uppercase sm:hover:text-sm hover:text-sm bg-red-500/50 my-custom-class"
></div>`,
},
],
blocklist: ['font', 'uppercase', 'hover:text-sm', 'bg-red-500/50', 'my-custom-class'],
it('can block classes matched literally', () => {
let config = {
content: [
{
raw: html`<div
class="font-bold uppercase sm:hover:text-sm hover:text-sm bg-red-500/50 my-custom-class"
></div>`,
},
],
blocklist: ['font', 'uppercase', 'hover:text-sm', 'bg-red-500/50', 'my-custom-class'],
}
let input = css`
@tailwind utilities;
.my-custom-class {
color: red;
}
`
let input = css`
@tailwind utilities;
return run(input, config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
.font-bold {
font-weight: 700;
}
.my-custom-class {
color: red;
}
`
return run(input, config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
.font-bold {
font-weight: 700;
@media (min-width: 640px) {
.sm\:hover\:text-sm:hover {
font-size: 0.875rem;
line-height: 1.25rem;
}
.my-custom-class {
color: red;
}
@media (min-width: 640px) {
.sm\:hover\:text-sm:hover {
font-size: 0.875rem;
line-height: 1.25rem;
}
}
`)
})
})
it('can block classes inside @layer', () => {
let config = {
content: [
{
raw: html`<div class="font-bold my-custom-class"></div>`,
},
],
blocklist: ['my-custom-class'],
}
let input = css`
@tailwind utilities;
@layer utilities {
.my-custom-class {
color: red;
}
}
`
return run(input, config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
.font-bold {
font-weight: 700;
}
`)
})
})
it('blocklists do NOT support regexes', async () => {
let config = {
content: [{ raw: html`<div class="font-bold bg-[#f00d1e]"></div>` }],
blocklist: [/^bg-\[[^]+\]$/],
}
let result = await run('@tailwind utilities', config)
stable.expect(result.css).toMatchFormattedCss(css`
.bg-\[\#f00d1e\] {
--tw-bg-opacity: 1;
background-color: rgb(240 13 30 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
`)
})
})
it('can block classes inside @layer', () => {
let config = {
content: [
{
raw: html`<div class="font-bold my-custom-class"></div>`,
},
],
blocklist: ['my-custom-class'],
}
oxide.expect(result.css).toMatchFormattedCss(css`
.bg-\[\#f00d1e\] {
background-color: #f00d1e;
let input = css`
@tailwind utilities;
@layer utilities {
.my-custom-class {
color: red;
}
}
`
return run(input, config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
.font-bold {
font-weight: 700;
}
`)
})
})
it('blocklists do NOT support regexes', async () => {
let config = {
content: [{ raw: html`<div class="font-bold bg-[#f00d1e]"></div>` }],
blocklist: [/^bg-\[[^]+\]$/],
}
expect().toHaveBeenWarnedWith(['blocklist-invalid'])
})
let result = await run('@tailwind utilities', config)
it('can block classes generated by the safelist', () => {
let config = {
content: [{ raw: html`<div class="font-bold"></div>` }],
safelist: [{ pattern: /^bg-red-(400|500)$/ }],
blocklist: ['bg-red-500'],
expect(result.css).toMatchFormattedCss(css`
.bg-\[\#f00d1e\] {
--tw-bg-opacity: 1;
background-color: rgb(240 13 30 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
`)
return run('@tailwind utilities', config).then((result) => {
stable.expect(result.css).toMatchFormattedCss(css`
.bg-red-400 {
--tw-bg-opacity: 1;
background-color: rgb(248 113 113 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
`)
oxide.expect(result.css).toMatchFormattedCss(css`
.bg-red-400 {
background-color: #f87171;
}
.font-bold {
font-weight: 700;
}
`)
})
expect().toHaveBeenWarnedWith(['blocklist-invalid'])
})
it('can block classes generated by the safelist', () => {
let config = {
content: [{ raw: html`<div class="font-bold"></div>` }],
safelist: [{ pattern: /^bg-red-(400|500)$/ }],
blocklist: ['bg-red-500'],
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.bg-red-400 {
--tw-bg-opacity: 1;
background-color: rgb(248 113 113 / var(--tw-bg-opacity));
}
.font-bold {
font-weight: 700;
}
`)
})
})
+131 -211
View File
@@ -1,114 +1,119 @@
import { crosscheck, run, html, css, defaults } from './util/run'
import { run, html, css, defaults } from './util/run'
crosscheck(({ stable, oxide }) => {
test('collapse adjacent rules', () => {
let config = {
content: [
{
raw: html`
<div class="custom-component"></div>
<div class="sm:custom-component"></div>
<div class="md:custom-component"></div>
<div class="lg:custom-component"></div>
<div class="font-bold"></div>
<div class="sm:font-bold"></div>
<div class="sm:text-center"></div>
<div class="md:font-bold"></div>
<div class="md:text-center"></div>
<div class="lg:font-bold"></div>
<div class="lg:text-center"></div>
<div class="some-apply-thing"></div>
`,
},
],
corePlugins: { preflight: false },
theme: {},
plugins: [
function ({ addVariant }) {
addVariant('foo-bar', '@supports (foo: bar)')
},
],
test('collapse adjacent rules', () => {
let config = {
content: [
{
raw: html`
<div class="custom-component"></div>
<div class="sm:custom-component"></div>
<div class="md:custom-component"></div>
<div class="lg:custom-component"></div>
<div class="font-bold"></div>
<div class="sm:font-bold"></div>
<div class="sm:text-center"></div>
<div class="md:font-bold"></div>
<div class="md:text-center"></div>
<div class="lg:font-bold"></div>
<div class="lg:text-center"></div>
<div class="some-apply-thing"></div>
`,
},
],
corePlugins: { preflight: false },
theme: {},
plugins: [
function ({ addVariant }) {
addVariant('foo-bar', '@supports (foo: bar)')
},
],
}
let input = css`
@tailwind base;
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter.woff2') format('woff2'), url('/fonts/Inter.woff') format('woff');
}
let input = css`
@tailwind base;
@font-face {
font-family: 'Gilroy';
src: url('/fonts/Gilroy.woff2') format('woff2'), url('/fonts/Gilroy.woff') format('woff');
}
@page {
margin: 1cm;
}
@tailwind components;
@tailwind utilities;
@layer base {
@font-face {
font-family: 'Inter';
font-family: 'Poppins';
src: url('/fonts/Poppins.woff2') format('woff2'), url('/fonts/Poppins.woff') format('woff');
}
@font-face {
font-family: 'Proxima Nova';
src: url('/fonts/ProximaNova.woff2') format('woff2'),
url('/fonts/ProximaNova.woff') format('woff');
}
}
.foo,
.bar {
color: black;
}
.foo,
.bar {
font-weight: 700;
}
.some-apply-thing {
@apply foo-bar:md:text-black foo-bar:md:font-bold foo-bar:text-black foo-bar:font-bold md:font-bold md:text-black;
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@font-face {
font-family: Poppins;
src: url('/fonts/Poppins.woff2') format('woff2'), url('/fonts/Poppins.woff') format('woff');
}
@font-face {
font-family: Proxima Nova;
src: url('/fonts/ProximaNova.woff2') format('woff2'),
url('/fonts/ProximaNova.woff') format('woff');
}
${defaults}
@font-face {
font-family: Inter;
src: url('/fonts/Inter.woff2') format('woff2'), url('/fonts/Inter.woff') format('woff');
}
@font-face {
font-family: 'Gilroy';
font-family: Gilroy;
src: url('/fonts/Gilroy.woff2') format('woff2'), url('/fonts/Gilroy.woff') format('woff');
}
@page {
margin: 1cm;
}
@tailwind components;
@tailwind utilities;
@layer base {
@font-face {
font-family: 'Poppins';
src: url('/fonts/Poppins.woff2') format('woff2'),
url('/fonts/Poppins.woff') format('woff');
}
@font-face {
font-family: 'Proxima Nova';
src: url('/fonts/ProximaNova.woff2') format('woff2'),
url('/fonts/ProximaNova.woff') format('woff');
}
}
.foo,
.bar {
color: black;
}
.foo,
.bar {
.font-bold {
font-weight: 700;
}
.some-apply-thing {
@apply foo-bar:md:text-black foo-bar:md:font-bold foo-bar:text-black foo-bar:font-bold md:font-bold md:text-black;
.foo,
.bar {
color: #000;
font-weight: 700;
}
`
return run(input, config).then((result) => {
stable.expect(result.css).toMatchFormattedCss(css`
@font-face {
font-family: Poppins;
src: url('/fonts/Poppins.woff2') format('woff2'),
url('/fonts/Poppins.woff') format('woff');
}
@font-face {
font-family: Proxima Nova;
src: url('/fonts/ProximaNova.woff2') format('woff2'),
url('/fonts/ProximaNova.woff') format('woff');
}
${defaults}
@font-face {
font-family: Inter;
src: url('/fonts/Inter.woff2') format('woff2'), url('/fonts/Inter.woff') format('woff');
}
@font-face {
font-family: Gilroy;
src: url('/fonts/Gilroy.woff2') format('woff2'), url('/fonts/Gilroy.woff') format('woff');
}
@page {
margin: 1cm;
}
.font-bold {
@supports (foo: bar) {
.some-apply-thing {
--tw-text-opacity: 1;
color: rgb(0 0 0 / var(--tw-text-opacity));
font-weight: 700;
}
.foo,
.bar {
color: #000;
}
@media (min-width: 768px) {
.some-apply-thing {
--tw-text-opacity: 1;
color: rgb(0 0 0 / var(--tw-text-opacity));
font-weight: 700;
}
@supports (foo: bar) {
.some-apply-thing {
--tw-text-opacity: 1;
color: rgb(0 0 0 / var(--tw-text-opacity));
font-weight: 700;
}
}
}
@supports (foo: bar) {
@media (min-width: 768px) {
.some-apply-thing {
--tw-text-opacity: 1;
@@ -116,134 +121,49 @@ crosscheck(({ stable, oxide }) => {
font-weight: 700;
}
}
@supports (foo: bar) {
@media (min-width: 768px) {
.some-apply-thing {
--tw-text-opacity: 1;
color: rgb(0 0 0 / var(--tw-text-opacity));
font-weight: 700;
}
}
}
@media (min-width: 640px) {
.sm\:text-center {
text-align: center;
}
@media (min-width: 640px) {
.sm\:text-center {
text-align: center;
}
.sm\:font-bold {
font-weight: 700;
}
}
@media (min-width: 768px) {
.md\:text-center {
text-align: center;
}
.md\:font-bold {
font-weight: 700;
}
}
@media (min-width: 1024px) {
.lg\:text-center {
text-align: center;
}
.lg\:font-bold {
font-weight: 700;
}
}
`)
oxide.expect(result.css).toMatchFormattedCss(css`
@font-face {
font-family: Poppins;
src: url('/fonts/Poppins.woff2') format('woff2'),
url('/fonts/Poppins.woff') format('woff');
}
@font-face {
font-family: Proxima Nova;
src: url('/fonts/ProximaNova.woff2') format('woff2'),
url('/fonts/ProximaNova.woff') format('woff');
}
${defaults}
@font-face {
font-family: Inter;
src: url('/fonts/Inter.woff2') format('woff2'), url('/fonts/Inter.woff') format('woff');
}
@font-face {
font-family: Gilroy;
src: url('/fonts/Gilroy.woff2') format('woff2'), url('/fonts/Gilroy.woff') format('woff');
}
@page {
margin: 1cm;
}
.font-bold {
.sm\:font-bold {
font-weight: 700;
}
.foo,
.bar {
color: #000;
}
@media (min-width: 768px) {
.md\:text-center {
text-align: center;
}
.md\:font-bold {
font-weight: 700;
}
@supports (foo: bar) {
.some-apply-thing {
color: #000;
font-weight: 700;
}
}
@media (min-width: 1024px) {
.lg\:text-center {
text-align: center;
}
@media (min-width: 768px) {
.some-apply-thing {
color: #000;
font-weight: 700;
}
.lg\:font-bold {
font-weight: 700;
}
@supports (foo: bar) {
@media (min-width: 768px) {
.some-apply-thing {
color: #000;
font-weight: 700;
}
}
}
@media (min-width: 640px) {
.sm\:text-center {
text-align: center;
}
.sm\:font-bold {
font-weight: 700;
}
}
@media (min-width: 768px) {
.md\:text-center {
text-align: center;
}
.md\:font-bold {
font-weight: 700;
}
}
@media (min-width: 1024px) {
.lg\:text-center {
text-align: center;
}
.lg\:font-bold {
font-weight: 700;
}
}
`)
})
}
`)
})
})
test('duplicate url imports does not break rule collapsing', () => {
let config = {
content: [{ raw: html`` }],
corePlugins: { preflight: false },
}
test('duplicate url imports does not break rule collapsing', () => {
let config = {
content: [{ raw: html`` }],
corePlugins: { preflight: false },
}
let input = css`
@import url('https://example.com');
@import url('https://example.com');
`
let input = css`
@import url('https://example.com');
@import url('https://example.com');
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@import 'https://example.com';
`)
})
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@import 'https://example.com';
`)
})
})
+151 -153
View File
@@ -1,177 +1,175 @@
import { crosscheck, run, html, css } from './util/run'
import { run, html, css } from './util/run'
crosscheck(() => {
it('should collapse duplicate declarations with the same units (px)', () => {
let config = {
content: [{ raw: html`<div class="example"></div>` }],
corePlugins: { preflight: false },
plugins: [],
}
it('should collapse duplicate declarations with the same units (px)', () => {
let config = {
content: [{ raw: html`<div class="example"></div>` }],
corePlugins: { preflight: false },
plugins: [],
}
let input = css`
@tailwind utilities;
let input = css`
@tailwind utilities;
@layer utilities {
.example {
height: 100px;
height: 200px;
}
@layer utilities {
.example {
height: 100px;
height: 200px;
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.example {
height: 200px;
}
`)
})
})
it('should collapse duplicate declarations with the same units (no unit)', () => {
let config = {
content: [{ raw: html`<div class="example"></div>` }],
corePlugins: { preflight: false },
plugins: [],
}
`
let input = css`
@tailwind utilities;
@layer utilities {
.example {
line-height: 3;
line-height: 2;
}
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.example {
height: 200px;
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.example {
line-height: 2;
}
`)
})
`)
})
})
it('should collapse duplicate declarations with the same units (no unit)', () => {
let config = {
content: [{ raw: html`<div class="example"></div>` }],
corePlugins: { preflight: false },
plugins: [],
}
let input = css`
@tailwind utilities;
@layer utilities {
.example {
line-height: 3;
line-height: 2;
}
}
`
it('should not collapse duplicate declarations with the different units', () => {
let config = {
content: [{ raw: html`<div class="example"></div>` }],
corePlugins: { preflight: false },
plugins: [],
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.example {
line-height: 2;
}
`)
})
})
it('should not collapse duplicate declarations with the different units', () => {
let config = {
content: [{ raw: html`<div class="example"></div>` }],
corePlugins: { preflight: false },
plugins: [],
}
let input = css`
@tailwind utilities;
@layer utilities {
.example {
height: 100px;
height: 50%;
}
}
let input = css`
@tailwind utilities;
`
@layer utilities {
.example {
height: 100px;
height: 50%;
}
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.example {
height: 100px;
height: 50%;
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.example {
height: 100px;
height: 50%;
}
`)
})
`)
})
it('should collapse the duplicate declarations with the same unit, but leave the ones with different units', () => {
let config = {
content: [{ raw: html`<div class="example"></div>` }],
corePlugins: { preflight: false },
plugins: [],
})
it('should collapse the duplicate declarations with the same unit, but leave the ones with different units', () => {
let config = {
content: [{ raw: html`<div class="example"></div>` }],
corePlugins: { preflight: false },
plugins: [],
}
let input = css`
@tailwind utilities;
@layer utilities {
.example {
height: 100px;
height: 50%;
height: 20vh;
height: 200px;
height: 100%;
height: 30vh;
}
}
let input = css`
@tailwind utilities;
@layer utilities {
.example {
height: 100px;
height: 50%;
height: 20vh;
height: 200px;
height: 100%;
height: 30vh;
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.example {
height: 200px;
height: 100%;
height: 30vh;
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.example {
height: 200px;
height: 100%;
height: 30vh;
}
`)
})
`)
})
it('should collapse the duplicate declarations with the exact same value', () => {
let config = {
content: [{ raw: html`<div class="example"></div>` }],
corePlugins: { preflight: false },
plugins: [],
})
it('should collapse the duplicate declarations with the exact same value', () => {
let config = {
content: [{ raw: html`<div class="example"></div>` }],
corePlugins: { preflight: false },
plugins: [],
}
let input = css`
@tailwind utilities;
@layer utilities {
.example {
height: var(--value);
color: blue;
height: var(--value);
}
}
let input = css`
@tailwind utilities;
`
@layer utilities {
.example {
height: var(--value);
color: blue;
height: var(--value);
}
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.example {
color: #00f;
height: var(--value);
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.example {
color: #00f;
height: var(--value);
}
`)
})
`)
})
it('should work on a real world example', () => {
let config = {
content: [{ raw: html`<div class="h-available"></div>` }],
corePlugins: { preflight: false },
plugins: [],
})
it('should work on a real world example', () => {
let config = {
content: [{ raw: html`<div class="h-available"></div>` }],
corePlugins: { preflight: false },
plugins: [],
}
let input = css`
@tailwind utilities;
@layer utilities {
.h-available {
height: 100%;
height: 100vh;
height: -webkit-fill-available;
}
}
let input = css`
@tailwind utilities;
@layer utilities {
.h-available {
height: 100%;
height: 100vh;
height: -webkit-fill-available;
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.h-available {
height: 100%;
height: 100vh;
height: -webkit-fill-available;
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.h-available {
height: 100%;
height: 100vh;
height: -webkit-fill-available;
}
`)
})
`)
})
})
+208 -238
View File
@@ -1,264 +1,234 @@
import { crosscheck, run, html, css } from './util/run'
import { run, html, css } from './util/run'
crosscheck(({ stable, oxide }) => {
test('basic color opacity modifier', async () => {
let config = {
content: [{ raw: html`<div class="bg-red-500/50"></div>` }],
}
test('basic color opacity modifier', async () => {
let config = {
content: [{ raw: html`<div class="bg-red-500/50"></div>` }],
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.bg-red-500\/50 {
background-color: #ef444480;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.bg-red-500\/50 {
background-color: #ef444480;
}
`)
})
})
test('colors with slashes are matched first', async () => {
let config = {
content: [{ raw: html`<div class="bg-red-500/50"></div>` }],
theme: {
extend: {
colors: {
'red-500/50': '#ff0000',
},
test('colors with slashes are matched first', async () => {
let config = {
content: [{ raw: html`<div class="bg-red-500/50"></div>` }],
theme: {
extend: {
colors: {
'red-500/50': '#ff0000',
},
},
}
return run('@tailwind utilities', config).then((result) => {
stable.expect(result.css).toMatchFormattedCss(css`
.bg-red-500\/50 {
--tw-bg-opacity: 1;
background-color: rgb(255 0 0 / var(--tw-bg-opacity));
}
`)
oxide.expect(result.css).toMatchFormattedCss(css`
.bg-red-500\/50 {
background-color: red;
}
`)
})
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.bg-red-500\/50 {
--tw-bg-opacity: 1;
background-color: rgb(255 0 0 / var(--tw-bg-opacity));
}
`)
})
test('arbitrary color opacity modifier', async () => {
let config = {
content: [{ raw: html`<div class="bg-red-500/[var(--opacity)]"></div>` }],
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.bg-red-500\/\[var\(--opacity\)\] {
background-color: rgb(239 68 68 / var(--opacity));
}
`)
})
})
test('arbitrary color opacity modifier', async () => {
let config = {
content: [{ raw: html`<div class="bg-red-500/[var(--opacity)]"></div>` }],
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.bg-red-500\/\[var\(--opacity\)\] {
background-color: rgb(239 68 68 / var(--opacity));
}
`)
})
})
test('missing alpha generates nothing', async () => {
let config = {
content: [{ raw: html`<div class="bg-red-500/"></div>` }],
}
test('missing alpha generates nothing', async () => {
let config = {
content: [{ raw: html`<div class="bg-red-500/"></div>` }],
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(``)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(``)
})
test('arbitrary color with opacity from scale', async () => {
let config = {
content: [{ raw: 'bg-[wheat]/50' }],
theme: {},
plugins: [],
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.bg-\[wheat\]\/50 {
background-color: #f5deb380;
}
`)
})
})
test('arbitrary color with opacity from scale', async () => {
let config = {
content: [{ raw: 'bg-[wheat]/50' }],
theme: {},
plugins: [],
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.bg-\[wheat\]\/50 {
background-color: #f5deb380;
}
`)
})
test('arbitrary color with arbitrary opacity', async () => {
let config = {
content: [{ raw: 'bg-[#bada55]/[0.2]' }],
theme: {},
plugins: [],
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.bg-\[\#bada55\]\/\[0\.2\] {
background-color: #bada5533;
}
`)
})
})
test('arbitrary color with arbitrary opacity', async () => {
let config = {
content: [{ raw: 'bg-[#bada55]/[0.2]' }],
theme: {},
plugins: [],
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.bg-\[\#bada55\]\/\[0\.2\] {
background-color: #bada5533;
}
`)
})
})
test('undefined theme color with opacity from scale', async () => {
let config = {
content: [{ raw: 'bg-garbage/50' }],
theme: {},
plugins: [],
}
test('undefined theme color with opacity from scale', async () => {
let config = {
content: [{ raw: 'bg-garbage/50' }],
theme: {},
plugins: [],
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(``)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(``)
})
test('values not in the opacity config are ignored', async () => {
let config = {
content: [{ raw: html`<div class="bg-red-500/29"></div>` }],
theme: {
opacity: {
0: '0',
25: '0.25',
5: '0.5',
75: '0.75',
100: '1',
},
})
test('values not in the opacity config are ignored', async () => {
let config = {
content: [{ raw: html`<div class="bg-red-500/29"></div>` }],
theme: {
opacity: {
0: '0',
25: '0.25',
5: '0.5',
75: '0.75',
100: '1',
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(``)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(``)
})
test('function colors are supported', async () => {
let config = {
content: [{ raw: html`<div class="bg-blue/50"></div>` }],
theme: {
colors: {
blue: ({ opacityValue }) => {
return `rgba(var(--colors-blue), ${opacityValue})`
},
})
test('function colors are supported', async () => {
let config = {
content: [{ raw: html`<div class="bg-blue/50"></div>` }],
theme: {
colors: {
blue: ({ opacityValue }) => {
return `rgba(var(--colors-blue), ${opacityValue})`
},
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.bg-blue\/50 {
background-color: rgba(var(--colors-blue), 0.5);
}
`)
})
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.bg-blue\/50 {
background-color: rgba(var(--colors-blue), 0.5);
}
`)
})
test('utilities that support any type are supported', async () => {
let config = {
content: [
{
raw: html`
<div class="from-red-500/50"></div>
<div class="fill-red-500/25"></div>
<div class="placeholder-red-500/75"></div>
`,
},
],
theme: {
extend: {
fill: (theme) => theme('colors'),
},
})
test('utilities that support any type are supported', async () => {
let config = {
content: [
{
raw: html`
<div class="from-red-500/50"></div>
<div class="fill-red-500/25"></div>
<div class="placeholder-red-500/75"></div>
`,
},
],
theme: {
extend: {
fill: (theme) => theme('colors'),
},
plugins: [],
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.from-red-500\/50 {
--tw-gradient-from: #ef444480 var(--tw-gradient-from-position);
--tw-gradient-to: #ef444400 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.fill-red-500\/25 {
fill: #ef444440;
}
.placeholder-red-500\/75::placeholder {
color: #ef4444bf;
}
`)
})
},
plugins: [],
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.from-red-500\/50 {
--tw-gradient-from: #ef444480 var(--tw-gradient-from-position);
--tw-gradient-to: #ef444400 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.fill-red-500\/25 {
fill: #ef444440;
}
.placeholder-red-500\/75::placeholder {
color: #ef4444bf;
}
`)
})
test('opacity modifier in combination with partial custom properties', async () => {
let config = {
content: [
{
raw: html`
<div class="bg-[hsl(var(--foo),50%,50%)]"></div>
<div class="bg-[hsl(123,var(--foo),50%)]"></div>
<div class="bg-[hsl(123,50%,var(--foo))]"></div>
<div class="bg-[hsl(var(--foo),50%,50%)]/50"></div>
<div class="bg-[hsl(123,var(--foo),50%)]/50"></div>
<div class="bg-[hsl(123,50%,var(--foo))]/50"></div>
<div class="bg-[hsl(var(--foo),var(--bar),var(--baz))]/50"></div>
`,
},
],
corePlugins: { preflight: false },
plugins: [],
}
let input = css`
@tailwind utilities;
`
return run(input, config).then((result) => {
stable.expect(result.css).toMatchFormattedCss(css`
.bg-\[hsl\(123\,50\%\,var\(--foo\)\)\] {
--tw-bg-opacity: 1;
background-color: hsl(123 50% var(--foo) / var(--tw-bg-opacity));
}
.bg-\[hsl\(123\,50\%\,var\(--foo\)\)\]\/50 {
background-color: hsl(123 50% var(--foo) / 0.5);
}
.bg-\[hsl\(123\,var\(--foo\)\,50\%\)\] {
--tw-bg-opacity: 1;
background-color: hsl(123 var(--foo) 50% / var(--tw-bg-opacity));
}
.bg-\[hsl\(123\,var\(--foo\)\,50\%\)\]\/50 {
background-color: hsl(123 var(--foo) 50% / 0.5);
}
.bg-\[hsl\(var\(--foo\)\,50\%\,50\%\)\] {
--tw-bg-opacity: 1;
background-color: hsl(var(--foo) 50% 50% / var(--tw-bg-opacity));
}
.bg-\[hsl\(var\(--foo\)\,50\%\,50\%\)\]\/50 {
background-color: hsl(var(--foo) 50% 50% / 0.5);
}
.bg-\[hsl\(var\(--foo\)\,var\(--bar\)\,var\(--baz\)\)\]\/50 {
background-color: hsl(var(--foo) var(--bar) var(--baz) / 0.5);
}
`)
oxide.expect(result.css).toMatchFormattedCss(css`
.bg-\[hsl\(123\,50\%\,var\(--foo\)\)\] {
background-color: hsl(123, 50%, var(--foo));
}
.bg-\[hsl\(123\,50\%\,var\(--foo\)\)\]\/50 {
background-color: hsl(123 50% var(--foo) / 0.5);
}
.bg-\[hsl\(123\,var\(--foo\)\,50\%\)\] {
background-color: hsl(123, var(--foo), 50%);
}
.bg-\[hsl\(123\,var\(--foo\)\,50\%\)\]\/50 {
background-color: hsl(123 var(--foo) 50% / 0.5);
}
.bg-\[hsl\(var\(--foo\)\,50\%\,50\%\)\] {
background-color: hsl(var(--foo), 50%, 50%);
}
.bg-\[hsl\(var\(--foo\)\,50\%\,50\%\)\]\/50 {
background-color: hsl(var(--foo) 50% 50% / 0.5);
}
.bg-\[hsl\(var\(--foo\)\,var\(--bar\)\,var\(--baz\)\)\]\/50 {
background-color: hsl(var(--foo) var(--bar) var(--baz) / 0.5);
}
`)
})
})
test('opacity modifier in combination with partial custom properties', async () => {
let config = {
content: [
{
raw: html`
<div class="bg-[hsl(var(--foo),50%,50%)]"></div>
<div class="bg-[hsl(123,var(--foo),50%)]"></div>
<div class="bg-[hsl(123,50%,var(--foo))]"></div>
<div class="bg-[hsl(var(--foo),50%,50%)]/50"></div>
<div class="bg-[hsl(123,var(--foo),50%)]/50"></div>
<div class="bg-[hsl(123,50%,var(--foo))]/50"></div>
<div class="bg-[hsl(var(--foo),var(--bar),var(--baz))]/50"></div>
`,
},
],
corePlugins: { preflight: false },
plugins: [],
}
let input = css`
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.bg-\[hsl\(123\,50\%\,var\(--foo\)\)\] {
--tw-bg-opacity: 1;
background-color: hsl(123 50% var(--foo) / var(--tw-bg-opacity));
}
.bg-\[hsl\(123\,50\%\,var\(--foo\)\)\]\/50 {
background-color: hsl(123 50% var(--foo) / 0.5);
}
.bg-\[hsl\(123\,var\(--foo\)\,50\%\)\] {
--tw-bg-opacity: 1;
background-color: hsl(123 var(--foo) 50% / var(--tw-bg-opacity));
}
.bg-\[hsl\(123\,var\(--foo\)\,50\%\)\]\/50 {
background-color: hsl(123 var(--foo) 50% / 0.5);
}
.bg-\[hsl\(var\(--foo\)\,50\%\,50\%\)\] {
--tw-bg-opacity: 1;
background-color: hsl(var(--foo) 50% 50% / var(--tw-bg-opacity));
}
.bg-\[hsl\(var\(--foo\)\,50\%\,50\%\)\]\/50 {
background-color: hsl(var(--foo) 50% 50% / 0.5);
}
.bg-\[hsl\(var\(--foo\)\,var\(--bar\)\,var\(--baz\)\)\]\/50 {
background-color: hsl(var(--foo) var(--bar) var(--baz) / 0.5);
}
`)
})
})
+87 -90
View File
@@ -1,96 +1,93 @@
import { parseColor, formatColor } from '../src/util/color'
import { crosscheck } from './util/run'
crosscheck(() => {
describe('parseColor', () => {
it.each`
color | output
${'black'} | ${{ mode: 'rgb', color: ['0', '0', '0'], alpha: undefined }}
${'#0088cc'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: undefined }}
${'#08c'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: undefined }}
${'#0088cc99'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: '0.6' }}
${'#08c9'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: '0.6' }}
${'rgb(0, 30, 60)'} | ${{ mode: 'rgb', color: ['0', '30', '60'], alpha: undefined }}
${'rgba(0, 30, 60, 0.5)'} | ${{ mode: 'rgba', color: ['0', '30', '60'], alpha: '0.5' }}
${'rgb(0 30 60)'} | ${{ mode: 'rgb', color: ['0', '30', '60'], alpha: undefined }}
${'rgb(0 30 60 / 0.5)'} | ${{ mode: 'rgb', color: ['0', '30', '60'], alpha: '0.5' }}
${'rgb(var(--foo), 30, 60)'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', '60'], alpha: undefined }}
${'rgb(0, var(--foo), 60)'} | ${{ mode: 'rgb', color: ['0', 'var(--foo)', '60'], alpha: undefined }}
${'rgb(0, 30, var(--foo))'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: undefined }}
${'rgb(0, 30, var(--foo), 0.5)'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: '0.5' }}
${'rgb(var(--foo), 30, var(--bar))'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', 'var(--bar)'], alpha: undefined }}
${'rgb(var(--foo), var(--bar), var(--baz))'} | ${{ mode: 'rgb', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
${'rgb(var(--foo) 30 60)'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', '60'], alpha: undefined }}
${'rgb(0 var(--foo) 60)'} | ${{ mode: 'rgb', color: ['0', 'var(--foo)', '60'], alpha: undefined }}
${'rgb(0 30 var(--foo))'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: undefined }}
${'rgb(0 30 var(--foo) / 0.5)'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: '0.5' }}
${'rgb(var(--foo) 30 var(--bar))'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', 'var(--bar)'], alpha: undefined }}
${'rgb(var(--foo) var(--bar) var(--baz))'} | ${{ mode: 'rgb', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
${'hsl(0, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0', '30%', '60%'], alpha: undefined }}
${'hsl(0deg, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0deg', '30%', '60%'], alpha: undefined }}
${'hsl(0rad, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0rad', '30%', '60%'], alpha: undefined }}
${'hsl(0grad, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0grad', '30%', '60%'], alpha: undefined }}
${'hsl(0turn, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0turn', '30%', '60%'], alpha: undefined }}
${'hsla(0, 30%, 60%, 0.5)'} | ${{ mode: 'hsla', color: ['0', '30%', '60%'], alpha: '0.5' }}
${'hsla(0deg, 30%, 60%, 0.5)'} | ${{ mode: 'hsla', color: ['0deg', '30%', '60%'], alpha: '0.5' }}
${'hsla(0rad, 30%, 60%, 0.5)'} | ${{ mode: 'hsla', color: ['0rad', '30%', '60%'], alpha: '0.5' }}
${'hsla(0grad, 30%, 60%, 0.5)'} | ${{ mode: 'hsla', color: ['0grad', '30%', '60%'], alpha: '0.5' }}
${'hsla(0turn, 30%, 60%, 0.5)'} | ${{ mode: 'hsla', color: ['0turn', '30%', '60%'], alpha: '0.5' }}
${'hsl(0 30% 60%)'} | ${{ mode: 'hsl', color: ['0', '30%', '60%'], alpha: undefined }}
${'hsl(0deg 30% 60%)'} | ${{ mode: 'hsl', color: ['0deg', '30%', '60%'], alpha: undefined }}
${'hsl(0rad 30% 60%)'} | ${{ mode: 'hsl', color: ['0rad', '30%', '60%'], alpha: undefined }}
${'hsl(0grad 30% 60%)'} | ${{ mode: 'hsl', color: ['0grad', '30%', '60%'], alpha: undefined }}
${'hsl(0turn 30% 60%)'} | ${{ mode: 'hsl', color: ['0turn', '30%', '60%'], alpha: undefined }}
${'hsl(0 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0', '30%', '60%'], alpha: '0.5' }}
${'hsl(0deg 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0deg', '30%', '60%'], alpha: '0.5' }}
${'hsl(0rad 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0rad', '30%', '60%'], alpha: '0.5' }}
${'hsl(0grad 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0grad', '30%', '60%'], alpha: '0.5' }}
${'hsl(0turn 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0turn', '30%', '60%'], alpha: '0.5' }}
${'hsl(var(--foo), 30%, 60%)'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', '60%'], alpha: undefined }}
${'hsl(0, var(--foo), 60%)'} | ${{ mode: 'hsl', color: ['0', 'var(--foo)', '60%'], alpha: undefined }}
${'hsl(0, 30%, var(--foo))'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: undefined }}
${'hsl(0, 30%, var(--foo), 0.5)'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: '0.5' }}
${'hsl(var(--foo), 30%, var(--bar))'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', 'var(--bar)'], alpha: undefined }}
${'hsl(var(--foo), var(--bar), var(--baz))'} | ${{ mode: 'hsl', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
${'hsl(var(--foo) 30% 60%)'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', '60%'], alpha: undefined }}
${'hsl(0 var(--foo) 60%)'} | ${{ mode: 'hsl', color: ['0', 'var(--foo)', '60%'], alpha: undefined }}
${'hsl(0 30% var(--foo))'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: undefined }}
${'hsl(0 30% var(--foo) / 0.5)'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: '0.5' }}
${'hsl(var(--foo) 30% var(--bar))'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', 'var(--bar)'], alpha: undefined }}
${'hsl(var(--foo) var(--bar) var(--baz))'} | ${{ mode: 'hsl', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
${'rgba(var(--foo), 0.1)'} | ${{ mode: 'rgba', color: ['var(--foo)'], alpha: '0.1' }}
${'rgba(var(--foo), var(--alpha))'} | ${{ mode: 'rgba', color: ['var(--foo)'], alpha: 'var(--alpha)' }}
${'hsla(var(--foo), 0.1)'} | ${{ mode: 'hsla', color: ['var(--foo)'], alpha: '0.1' }}
${'hsla(var(--foo), var(--alpha))'} | ${{ mode: 'hsla', color: ['var(--foo)'], alpha: 'var(--alpha)' }}
${'transparent'} | ${{ mode: 'rgb', color: ['0', '0', '0'], alpha: '0' }}
`('should parse "$color" to the correct value', ({ color, output }) => {
expect(parseColor(color)).toEqual(output)
})
it.each`
color
${'var(--my-color)'}
${'currentColor'}
${'inherit'}
${'initial'}
${'revert'}
${'unset'}
`('should return `null` for unparseable color "$color"', ({ color }) => {
expect(parseColor(color)).toBe(null)
})
describe('parseColor', () => {
it.each`
color | output
${'black'} | ${{ mode: 'rgb', color: ['0', '0', '0'], alpha: undefined }}
${'#0088cc'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: undefined }}
${'#08c'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: undefined }}
${'#0088cc99'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: '0.6' }}
${'#08c9'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: '0.6' }}
${'rgb(0, 30, 60)'} | ${{ mode: 'rgb', color: ['0', '30', '60'], alpha: undefined }}
${'rgba(0, 30, 60, 0.5)'} | ${{ mode: 'rgba', color: ['0', '30', '60'], alpha: '0.5' }}
${'rgb(0 30 60)'} | ${{ mode: 'rgb', color: ['0', '30', '60'], alpha: undefined }}
${'rgb(0 30 60 / 0.5)'} | ${{ mode: 'rgb', color: ['0', '30', '60'], alpha: '0.5' }}
${'rgb(var(--foo), 30, 60)'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', '60'], alpha: undefined }}
${'rgb(0, var(--foo), 60)'} | ${{ mode: 'rgb', color: ['0', 'var(--foo)', '60'], alpha: undefined }}
${'rgb(0, 30, var(--foo))'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: undefined }}
${'rgb(0, 30, var(--foo), 0.5)'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: '0.5' }}
${'rgb(var(--foo), 30, var(--bar))'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', 'var(--bar)'], alpha: undefined }}
${'rgb(var(--foo), var(--bar), var(--baz))'} | ${{ mode: 'rgb', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
${'rgb(var(--foo) 30 60)'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', '60'], alpha: undefined }}
${'rgb(0 var(--foo) 60)'} | ${{ mode: 'rgb', color: ['0', 'var(--foo)', '60'], alpha: undefined }}
${'rgb(0 30 var(--foo))'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: undefined }}
${'rgb(0 30 var(--foo) / 0.5)'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: '0.5' }}
${'rgb(var(--foo) 30 var(--bar))'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', 'var(--bar)'], alpha: undefined }}
${'rgb(var(--foo) var(--bar) var(--baz))'} | ${{ mode: 'rgb', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
${'hsl(0, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0', '30%', '60%'], alpha: undefined }}
${'hsl(0deg, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0deg', '30%', '60%'], alpha: undefined }}
${'hsl(0rad, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0rad', '30%', '60%'], alpha: undefined }}
${'hsl(0grad, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0grad', '30%', '60%'], alpha: undefined }}
${'hsl(0turn, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0turn', '30%', '60%'], alpha: undefined }}
${'hsla(0, 30%, 60%, 0.5)'} | ${{ mode: 'hsla', color: ['0', '30%', '60%'], alpha: '0.5' }}
${'hsla(0deg, 30%, 60%, 0.5)'} | ${{ mode: 'hsla', color: ['0deg', '30%', '60%'], alpha: '0.5' }}
${'hsla(0rad, 30%, 60%, 0.5)'} | ${{ mode: 'hsla', color: ['0rad', '30%', '60%'], alpha: '0.5' }}
${'hsla(0grad, 30%, 60%, 0.5)'} | ${{ mode: 'hsla', color: ['0grad', '30%', '60%'], alpha: '0.5' }}
${'hsla(0turn, 30%, 60%, 0.5)'} | ${{ mode: 'hsla', color: ['0turn', '30%', '60%'], alpha: '0.5' }}
${'hsl(0 30% 60%)'} | ${{ mode: 'hsl', color: ['0', '30%', '60%'], alpha: undefined }}
${'hsl(0deg 30% 60%)'} | ${{ mode: 'hsl', color: ['0deg', '30%', '60%'], alpha: undefined }}
${'hsl(0rad 30% 60%)'} | ${{ mode: 'hsl', color: ['0rad', '30%', '60%'], alpha: undefined }}
${'hsl(0grad 30% 60%)'} | ${{ mode: 'hsl', color: ['0grad', '30%', '60%'], alpha: undefined }}
${'hsl(0turn 30% 60%)'} | ${{ mode: 'hsl', color: ['0turn', '30%', '60%'], alpha: undefined }}
${'hsl(0 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0', '30%', '60%'], alpha: '0.5' }}
${'hsl(0deg 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0deg', '30%', '60%'], alpha: '0.5' }}
${'hsl(0rad 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0rad', '30%', '60%'], alpha: '0.5' }}
${'hsl(0grad 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0grad', '30%', '60%'], alpha: '0.5' }}
${'hsl(0turn 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0turn', '30%', '60%'], alpha: '0.5' }}
${'hsl(var(--foo), 30%, 60%)'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', '60%'], alpha: undefined }}
${'hsl(0, var(--foo), 60%)'} | ${{ mode: 'hsl', color: ['0', 'var(--foo)', '60%'], alpha: undefined }}
${'hsl(0, 30%, var(--foo))'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: undefined }}
${'hsl(0, 30%, var(--foo), 0.5)'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: '0.5' }}
${'hsl(var(--foo), 30%, var(--bar))'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', 'var(--bar)'], alpha: undefined }}
${'hsl(var(--foo), var(--bar), var(--baz))'} | ${{ mode: 'hsl', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
${'hsl(var(--foo) 30% 60%)'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', '60%'], alpha: undefined }}
${'hsl(0 var(--foo) 60%)'} | ${{ mode: 'hsl', color: ['0', 'var(--foo)', '60%'], alpha: undefined }}
${'hsl(0 30% var(--foo))'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: undefined }}
${'hsl(0 30% var(--foo) / 0.5)'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: '0.5' }}
${'hsl(var(--foo) 30% var(--bar))'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', 'var(--bar)'], alpha: undefined }}
${'hsl(var(--foo) var(--bar) var(--baz))'} | ${{ mode: 'hsl', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
${'rgba(var(--foo), 0.1)'} | ${{ mode: 'rgba', color: ['var(--foo)'], alpha: '0.1' }}
${'rgba(var(--foo), var(--alpha))'} | ${{ mode: 'rgba', color: ['var(--foo)'], alpha: 'var(--alpha)' }}
${'hsla(var(--foo), 0.1)'} | ${{ mode: 'hsla', color: ['var(--foo)'], alpha: '0.1' }}
${'hsla(var(--foo), var(--alpha))'} | ${{ mode: 'hsla', color: ['var(--foo)'], alpha: 'var(--alpha)' }}
${'transparent'} | ${{ mode: 'rgb', color: ['0', '0', '0'], alpha: '0' }}
`('should parse "$color" to the correct value', ({ color, output }) => {
expect(parseColor(color)).toEqual(output)
})
describe('formatColor', () => {
it.each`
color | output
${{ mode: 'rgb', color: ['0', '0', '0'], alpha: undefined }} | ${'rgb(0 0 0)'}
${{ mode: 'rgb', color: ['0', '136', '204'], alpha: undefined }} | ${'rgb(0 136 204)'}
${{ mode: 'rgb', color: ['0', '136', '204'], alpha: '0.6' }} | ${'rgb(0 136 204 / 0.6)'}
${{ mode: 'hsl', color: ['0', '0%', '0%'], alpha: undefined }} | ${'hsl(0 0% 0%)'}
${{ mode: 'hsl', color: ['0', '136%', '204%'], alpha: undefined }} | ${'hsl(0 136% 204%)'}
${{ mode: 'hsl', color: ['0', '136%', '204%'], alpha: '0.6' }} | ${'hsl(0 136% 204% / 0.6)'}
`('should format the color pieces into a proper "$output"', ({ color, output }) => {
expect(formatColor(color)).toEqual(output)
})
it.each`
color
${'var(--my-color)'}
${'currentColor'}
${'inherit'}
${'initial'}
${'revert'}
${'unset'}
`('should return `null` for unparseable color "$color"', ({ color }) => {
expect(parseColor(color)).toBe(null)
})
})
describe('formatColor', () => {
it.each`
color | output
${{ mode: 'rgb', color: ['0', '0', '0'], alpha: undefined }} | ${'rgb(0 0 0)'}
${{ mode: 'rgb', color: ['0', '136', '204'], alpha: undefined }} | ${'rgb(0 136 204)'}
${{ mode: 'rgb', color: ['0', '136', '204'], alpha: '0.6' }} | ${'rgb(0 136 204 / 0.6)'}
${{ mode: 'hsl', color: ['0', '0%', '0%'], alpha: undefined }} | ${'hsl(0 0% 0%)'}
${{ mode: 'hsl', color: ['0', '136%', '204%'], alpha: undefined }} | ${'hsl(0 136% 204%)'}
${{ mode: 'hsl', color: ['0', '136%', '204%'], alpha: '0.6' }} | ${'hsl(0 136% 204% / 0.6)'}
`('should format the color pieces into a proper "$output"', ({ color, output }) => {
expect(formatColor(color)).toEqual(output)
})
})
+64 -66
View File
@@ -1,76 +1,74 @@
import { crosscheck, run, html, css, defaults } from './util/run'
import { run, html, css, defaults } from './util/run'
crosscheck(() => {
it('should generate the partial selector, if only a partial is used (base layer)', () => {
let config = {
content: [{ raw: html`<div></div>` }],
corePlugins: { preflight: false },
}
it('should generate the partial selector, if only a partial is used (base layer)', () => {
let config = {
content: [{ raw: html`<div></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
let input = css`
@tailwind base;
@layer base {
:root {
font-weight: bold;
}
/* --- */
:root,
.a {
color: black;
}
@layer base {
:root {
font-weight: bold;
}
`
return run(input, config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
:root {
font-weight: bold;
}
:root,
.a {
color: #000;
}
${defaults}
`)
})
})
/* --- */
it('should generate the partial selector, if only a partial is used (utilities layer)', () => {
let config = {
content: [{ raw: html`<div class="a"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind utilities;
@layer utilities {
:root {
font-weight: bold;
}
/* --- */
:root,
.a {
color: black;
}
:root,
.a {
color: black;
}
`
}
`
return run(input, config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
:root {
font-weight: bold;
}
:root,
.a {
color: #000;
}
`)
})
return run(input, config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
:root {
font-weight: bold;
}
:root,
.a {
color: #000;
}
${defaults}
`)
})
})
it('should generate the partial selector, if only a partial is used (utilities layer)', () => {
let config = {
content: [{ raw: html`<div class="a"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind utilities;
@layer utilities {
:root {
font-weight: bold;
}
/* --- */
:root,
.a {
color: black;
}
}
`
return run(input, config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
:root {
font-weight: bold;
}
:root,
.a {
color: #000;
}
`)
})
})
+14 -17
View File
@@ -1,28 +1,25 @@
import configurePlugins from '../src/util/configurePlugins'
import { crosscheck } from './util/run'
crosscheck(() => {
test('setting a plugin to false removes it', () => {
const plugins = ['fontSize', 'display', 'backgroundPosition']
test('setting a plugin to false removes it', () => {
const plugins = ['fontSize', 'display', 'backgroundPosition']
const configuredPlugins = configurePlugins({ display: false }, plugins)
const configuredPlugins = configurePlugins({ display: false }, plugins)
expect(configuredPlugins).toEqual(['fontSize', 'backgroundPosition'])
})
expect(configuredPlugins).toEqual(['fontSize', 'backgroundPosition'])
})
test('passing only false removes all plugins', () => {
const plugins = ['fontSize', 'display', 'backgroundPosition']
test('passing only false removes all plugins', () => {
const plugins = ['fontSize', 'display', 'backgroundPosition']
const configuredPlugins = configurePlugins(false, plugins)
const configuredPlugins = configurePlugins(false, plugins)
expect(configuredPlugins).toEqual([])
})
expect(configuredPlugins).toEqual([])
})
test('passing an array safelists plugins', () => {
const plugins = ['fontSize', 'display', 'backgroundPosition']
test('passing an array safelists plugins', () => {
const plugins = ['fontSize', 'display', 'backgroundPosition']
const configuredPlugins = configurePlugins(['display'], plugins)
const configuredPlugins = configurePlugins(['display'], plugins)
expect(configuredPlugins).toEqual(['display'])
})
expect(configuredPlugins).toEqual(['display'])
})
+287 -289
View File
@@ -1,347 +1,345 @@
import { crosscheck, run, html, css } from './util/run'
import { run, html, css } from './util/run'
crosscheck(({}) => {
test('options are not required', () => {
let config = { content: [{ raw: html`<div class="container"></div>` }] }
test('options are not required', () => {
let config = { content: [{ raw: html`<div class="container"></div>` }] }
return run('@tailwind components', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
return run('@tailwind components', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.container {
width: 100%;
}
@media (min-width: 640px) {
.container {
width: 100%;
max-width: 640px;
}
@media (min-width: 640px) {
.container {
max-width: 640px;
}
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
}
@media (min-width: 1536px) {
.container {
max-width: 1536px;
}
}
`)
})
})
test('screens can be passed explicitly', () => {
let config = {
content: [{ raw: html`<div class="container"></div>` }],
theme: {
container: {
screens: ['400px', '500px'],
},
},
}
return run('@tailwind components', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
}
@media (min-width: 768px) {
.container {
width: 100%;
max-width: 768px;
}
@media (min-width: 400px) {
.container {
max-width: 400px;
}
}
@media (min-width: 500px) {
.container {
max-width: 500px;
}
}
`)
})
})
test('screens are ordered ascending by min-width', () => {
let config = {
content: [{ raw: html`<div class="container"></div>` }],
theme: {
container: {
screens: ['500px', '400px'],
},
},
}
return run('@tailwind components', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
}
@media (min-width: 1024px) {
.container {
width: 100%;
max-width: 1024px;
}
@media (min-width: 400px) {
.container {
max-width: 400px;
}
}
@media (min-width: 500px) {
.container {
max-width: 500px;
}
}
`)
})
})
test('screens are deduplicated by min-width', () => {
let config = {
content: [{ raw: html`<div class="container"></div>` }],
theme: {
container: {
screens: {
sm: '576px',
md: '768px',
'sm-only': { min: '576px', max: '767px' },
},
},
},
}
return run('@tailwind components', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
}
@media (min-width: 1280px) {
.container {
width: 100%;
max-width: 1280px;
}
@media (min-width: 576px) {
.container {
max-width: 576px;
}
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
}
`)
})
})
test('the container can be centered by default', () => {
let config = {
content: [{ raw: html`<div class="container"></div>` }],
theme: {
container: {
center: true,
},
},
}
return run('@tailwind components', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
}
@media (min-width: 1536px) {
.container {
width: 100%;
margin-left: auto;
margin-right: auto;
max-width: 1536px;
}
@media (min-width: 640px) {
.container {
max-width: 640px;
}
}
`)
})
})
test('screens can be passed explicitly', () => {
let config = {
content: [{ raw: html`<div class="container"></div>` }],
theme: {
container: {
screens: ['400px', '500px'],
},
},
}
return run('@tailwind components', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.container {
width: 100%;
}
@media (min-width: 400px) {
.container {
max-width: 400px;
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
}
@media (min-width: 500px) {
.container {
max-width: 500px;
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
}
`)
})
})
test('screens are ordered ascending by min-width', () => {
let config = {
content: [{ raw: html`<div class="container"></div>` }],
theme: {
container: {
screens: ['500px', '400px'],
},
},
}
return run('@tailwind components', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.container {
width: 100%;
}
@media (min-width: 400px) {
.container {
max-width: 400px;
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
}
@media (min-width: 500px) {
.container {
max-width: 500px;
}
@media (min-width: 1536px) {
.container {
max-width: 1536px;
}
}
`)
})
}
`)
})
})
test('horizontal padding can be included by default', () => {
let config = {
content: [{ raw: html`<div class="container"></div>` }],
theme: {
container: {
padding: '2rem',
test('screens are deduplicated by min-width', () => {
let config = {
content: [{ raw: html`<div class="container"></div>` }],
theme: {
container: {
screens: {
sm: '576px',
md: '768px',
'sm-only': { min: '576px', max: '767px' },
},
},
}
},
}
return run('@tailwind components', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
return run('@tailwind components', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.container {
width: 100%;
}
@media (min-width: 576px) {
.container {
max-width: 576px;
}
}
@media (min-width: 768px) {
.container {
width: 100%;
padding-left: 2rem;
padding-right: 2rem;
max-width: 768px;
}
@media (min-width: 640px) {
.container {
max-width: 640px;
}
}
`)
})
})
test('the container can be centered by default', () => {
let config = {
content: [{ raw: html`<div class="container"></div>` }],
theme: {
container: {
center: true,
},
},
}
return run('@tailwind components', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.container {
width: 100%;
margin-left: auto;
margin-right: auto;
}
@media (min-width: 640px) {
.container {
max-width: 640px;
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
@media (min-width: 1536px) {
.container {
max-width: 1536px;
}
}
@media (min-width: 1536px) {
.container {
max-width: 1536px;
}
`)
})
}
`)
})
})
test('responsive horizontal padding can be included by default', () => {
let config = {
content: [{ raw: html`<div class="container"></div>` }],
theme: {
screens: {
sm: '576px',
md: { min: '768px' },
lg: { 'min-width': '992px' },
xl: { min: '1200px', max: '1600px' },
},
container: {
padding: {
DEFAULT: '1rem',
sm: '2rem',
lg: '4rem',
xl: '5rem',
},
},
test('horizontal padding can be included by default', () => {
let config = {
content: [{ raw: html`<div class="container"></div>` }],
theme: {
container: {
padding: '2rem',
},
}
},
}
return run('@tailwind components', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
return run('@tailwind components', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.container {
width: 100%;
padding-left: 2rem;
padding-right: 2rem;
}
@media (min-width: 640px) {
.container {
max-width: 640px;
}
}
@media (min-width: 768px) {
.container {
width: 100%;
padding-left: 1rem;
padding-right: 1rem;
}
@media (min-width: 576px) {
.container {
max-width: 576px;
padding-left: 2rem;
padding-right: 2rem;
}
max-width: 768px;
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
@media (min-width: 992px) {
.container {
max-width: 992px;
padding-left: 4rem;
padding-right: 4rem;
}
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
@media (min-width: 1200px) {
.container {
max-width: 1200px;
padding-left: 5rem;
padding-right: 5rem;
}
}
@media (min-width: 1536px) {
.container {
max-width: 1536px;
}
`)
})
}
`)
})
})
test('setting all options at once', () => {
let config = {
content: [{ raw: html`<div class="container"></div>` }],
theme: {
container: {
screens: ['400px', '500px'],
center: true,
padding: '2rem',
test('responsive horizontal padding can be included by default', () => {
let config = {
content: [{ raw: html`<div class="container"></div>` }],
theme: {
screens: {
sm: '576px',
md: { min: '768px' },
lg: { 'min-width': '992px' },
xl: { min: '1200px', max: '1600px' },
},
container: {
padding: {
DEFAULT: '1rem',
sm: '2rem',
lg: '4rem',
xl: '5rem',
},
},
}
},
}
return run('@tailwind components', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
return run('@tailwind components', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.container {
width: 100%;
padding-left: 1rem;
padding-right: 1rem;
}
@media (min-width: 576px) {
.container {
width: 100%;
margin-left: auto;
margin-right: auto;
max-width: 576px;
padding-left: 2rem;
padding-right: 2rem;
}
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
}
@media (min-width: 992px) {
.container {
max-width: 992px;
padding-left: 4rem;
padding-right: 4rem;
}
}
@media (min-width: 1200px) {
.container {
max-width: 1200px;
padding-left: 5rem;
padding-right: 5rem;
}
@media (min-width: 400px) {
.container {
max-width: 400px;
}
}
`)
})
})
test('setting all options at once', () => {
let config = {
content: [{ raw: html`<div class="container"></div>` }],
theme: {
container: {
screens: ['400px', '500px'],
center: true,
padding: '2rem',
},
},
}
return run('@tailwind components', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.container {
width: 100%;
margin-left: auto;
margin-right: auto;
padding-left: 2rem;
padding-right: 2rem;
}
@media (min-width: 400px) {
.container {
max-width: 400px;
}
@media (min-width: 500px) {
.container {
max-width: 500px;
}
}
@media (min-width: 500px) {
.container {
max-width: 500px;
}
`)
})
}
`)
})
})
test('container can use variants', () => {
let config = {
content: [{ raw: html`<div class="lg:hover:container"></div>` }],
theme: {
container: {
screens: ['400px', '500px'],
},
test('container can use variants', () => {
let config = {
content: [{ raw: html`<div class="lg:hover:container"></div>` }],
theme: {
container: {
screens: ['400px', '500px'],
},
}
},
}
return run('@tailwind components', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@media (min-width: 1024px) {
return run('@tailwind components', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@media (min-width: 1024px) {
.lg\:hover\:container:hover {
width: 100%;
}
@media (min-width: 400px) {
.lg\:hover\:container:hover {
width: 100%;
max-width: 400px;
}
@media (min-width: 400px) {
.lg\:hover\:container:hover {
max-width: 400px;
}
}
@media (min-width: 500px) {
.lg\:hover\:container:hover {
max-width: 500px;
}
}
@media (min-width: 500px) {
.lg\:hover\:container:hover {
max-width: 500px;
}
}
`)
})
}
`)
})
})
+99 -103
View File
@@ -1,4 +1,4 @@
import { crosscheck, css } from './util/run'
import { css } from './util/run'
const fs = require('fs')
const path = require('path')
@@ -7,126 +7,122 @@ const tailwind = require('../src/index.js')
const sharedState = require('../src/lib/sharedState.js')
const configPath = path.resolve(__dirname, './context-reuse.tailwind.config.js')
crosscheck(({ stable, oxide }) => {
function run(input, config = {}, from = null) {
let { currentTestName } = expect.getState()
function run(input, config = {}, from = null) {
let { currentTestName } = expect.getState()
from = `${path.resolve(__filename)}?test=${currentTestName}&${from}`
from = `${path.resolve(__filename)}?test=${currentTestName}&${from}`
return postcss(tailwind(config)).process(input, { from })
return postcss(tailwind(config)).process(input, { from })
}
beforeEach(async () => {
let config = {
content: [path.resolve(__dirname, './context-reuse.test.html')],
corePlugins: { preflight: false },
}
beforeEach(async () => {
let config = {
content: [path.resolve(__dirname, './context-reuse.test.html')],
corePlugins: { preflight: false },
await fs.promises.writeFile(configPath, `module.exports = ${JSON.stringify(config)};`)
})
afterEach(async () => {
await fs.promises.unlink(configPath)
})
test('re-uses the context across multiple files with the same config', async () => {
let results = [
await run(`@tailwind utilities;`, configPath, `id=1`),
// Using @apply directives should still re-use the context
// They depend on the config but do not the other way around
await run(`body { @apply bg-blue-400; }`, configPath, `id=2`),
await run(`body { @apply text-red-400; }`, configPath, `id=3`),
await run(`body { @apply mb-4; }`, configPath, `id=4`),
]
let dependencies = results.map((result) => {
return result.messages
.filter((message) => message.type === 'dependency')
.map((message) => message.file)
})
// The content files don't have any utilities in them so this should be empty
expect(results[0].css).toMatchFormattedCss(css``)
// However, @apply is being used so we want to verify that they're being inlined into the CSS rules
expect(results[1].css).toMatchFormattedCss(css`
body {
--tw-bg-opacity: 1;
background-color: rgb(96 165 250 / var(--tw-bg-opacity));
}
`)
await fs.promises.writeFile(configPath, `module.exports = ${JSON.stringify(config)};`)
})
expect(results[2].css).toMatchFormattedCss(css`
body {
--tw-text-opacity: 1;
color: rgb(248 113 113 / var(--tw-text-opacity));
}
`)
afterEach(async () => {
await fs.promises.unlink(configPath)
})
expect(results[3].css).toMatchFormattedCss(css`
body {
margin-bottom: 1rem;
}
`)
oxide.test.todo('re-uses the context across multiple files with the same config')
stable.test('re-uses the context across multiple files with the same config', async () => {
let results = [
await run(`@tailwind utilities;`, configPath, `id=1`),
// Files with @tailwind directives depends on the PostCSS tree, config, AND any content files
expect(dependencies[0]).toEqual([
path.resolve(__dirname, 'context-reuse.test.html'),
path.resolve(__dirname, 'context-reuse.tailwind.config.js'),
])
// Using @apply directives should still re-use the context
// They depend on the config but do not the other way around
await run(`body { @apply bg-blue-400; }`, configPath, `id=2`),
await run(`body { @apply text-red-400; }`, configPath, `id=3`),
await run(`body { @apply mb-4; }`, configPath, `id=4`),
]
// @apply depends only on the containing PostCSS tree *and* the config file but no content files
// as they cannot affect the outcome of the @apply directives
expect(dependencies[1]).toEqual([path.resolve(__dirname, 'context-reuse.tailwind.config.js')])
let dependencies = results.map((result) => {
return result.messages
.filter((message) => message.type === 'dependency')
.map((message) => message.file)
})
expect(dependencies[2]).toEqual([path.resolve(__dirname, 'context-reuse.tailwind.config.js')])
// The content files don't have any utilities in them so this should be empty
expect(results[0].css).toMatchFormattedCss(css``)
expect(dependencies[3]).toEqual([path.resolve(__dirname, 'context-reuse.tailwind.config.js')])
// However, @apply is being used so we want to verify that they're being inlined into the CSS rules
expect(results[1].css).toMatchFormattedCss(css`
body {
--tw-bg-opacity: 1;
background-color: rgb(96 165 250 / var(--tw-bg-opacity));
}
`)
// And none of this should have resulted in multiple contexts being created
expect(sharedState.contextSourcesMap.size).toBe(1)
})
expect(results[2].css).toMatchFormattedCss(css`
body {
--tw-text-opacity: 1;
color: rgb(248 113 113 / var(--tw-text-opacity));
}
`)
test('updates layers when any CSS containing @tailwind directives changes', async () => {
let result
expect(results[3].css).toMatchFormattedCss(css`
body {
margin-bottom: 1rem;
}
`)
// Files with @tailwind directives depends on the PostCSS tree, config, AND any content files
expect(dependencies[0]).toEqual([
path.resolve(__dirname, 'context-reuse.test.html'),
path.resolve(__dirname, 'context-reuse.tailwind.config.js'),
])
// @apply depends only on the containing PostCSS tree *and* the config file but no content files
// as they cannot affect the outcome of the @apply directives
expect(dependencies[1]).toEqual([path.resolve(__dirname, 'context-reuse.tailwind.config.js')])
expect(dependencies[2]).toEqual([path.resolve(__dirname, 'context-reuse.tailwind.config.js')])
expect(dependencies[3]).toEqual([path.resolve(__dirname, 'context-reuse.tailwind.config.js')])
// And none of this should have resulted in multiple contexts being created
expect(sharedState.contextSourcesMap.size).toBe(1)
})
oxide.test.todo('updates layers when any CSS containing @tailwind directives changes')
stable.test('updates layers when any CSS containing @tailwind directives changes', async () => {
let result
// Compile the initial version once
let input = css`
@tailwind utilities;
@layer utilities {
.custom-utility {
color: orange;
}
}
`
result = await run(input, configPath, `id=1`)
expect(result.css).toMatchFormattedCss(css`
.only\:custom-utility:only-child {
// Compile the initial version once
let input = css`
@tailwind utilities;
@layer utilities {
.custom-utility {
color: orange;
}
`)
}
`
// Save the file with a change
input = css`
@tailwind utilities;
@layer utilities {
.custom-utility {
color: blue;
}
result = await run(input, configPath, `id=1`)
expect(result.css).toMatchFormattedCss(css`
.only\:custom-utility:only-child {
color: orange;
}
`)
// Save the file with a change
input = css`
@tailwind utilities;
@layer utilities {
.custom-utility {
color: blue;
}
`
}
`
result = await run(input, configPath, `id=1`)
result = await run(input, configPath, `id=1`)
expect(result.css).toMatchFormattedCss(css`
.only\:custom-utility:only-child {
color: #00f;
}
`)
})
expect(result.css).toMatchFormattedCss(css`
.only\:custom-utility:only-child {
color: #00f;
}
`)
})
+92 -100
View File
@@ -1,4 +1,4 @@
import { crosscheck, run, html, css } from './util/run'
import { run, html, css } from './util/run'
function customExtractor(content) {
let matches = content.match(/class="([^"]+)"/)
@@ -21,117 +21,109 @@ let expected = css`
}
`
crosscheck(({ stable, oxide }) => {
describe('modern', () => {
oxide.test.todo('extract.DEFAULT')
stable.test('extract.DEFAULT', () => {
let config = {
content: {
files: [{ raw: sharedHtml }],
extract: {
DEFAULT: customExtractor,
},
describe('modern', () => {
test('extract.DEFAULT', () => {
let config = {
content: {
files: [{ raw: sharedHtml }],
extract: {
DEFAULT: customExtractor,
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(expected)
})
})
oxide.test.todo('extract.{extension}')
stable.test('extract.{extension}', () => {
let config = {
content: {
files: [{ raw: sharedHtml }],
extract: {
html: customExtractor,
},
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(expected)
})
})
oxide.test.todo('extract function')
stable.test('extract function', () => {
let config = {
content: {
files: [{ raw: sharedHtml }],
extract: customExtractor,
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(expected)
})
})
oxide.test.todo('raw content with extension')
stable.test('raw content with extension', () => {
let config = {
content: {
files: [
{
raw: sharedHtml,
extension: 'html',
},
],
extract: {
html: () => ['invisible'],
},
},
corePlugins: { preflight: false },
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.invisible {
visibility: hidden;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(expected)
})
})
describe('legacy', () => {
oxide.test.todo('defaultExtractor')
stable.test('defaultExtractor', () => {
let config = {
content: {
files: [{ raw: sharedHtml }],
options: {
defaultExtractor: customExtractor,
},
test('extract.{extension}', () => {
let config = {
content: {
files: [{ raw: sharedHtml }],
extract: {
html: customExtractor,
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(expected)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(expected)
})
})
oxide.test.todo('extractors array')
stable.test('extractors array', () => {
let config = {
content: {
files: [{ raw: sharedHtml }],
options: {
extractors: [
{
extractor: customExtractor,
extensions: ['html'],
},
],
test('extract function', () => {
let config = {
content: {
files: [{ raw: sharedHtml }],
extract: customExtractor,
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(expected)
})
})
test('raw content with extension', () => {
let config = {
content: {
files: [
{
raw: sharedHtml,
extension: 'html',
},
],
extract: {
html: () => ['invisible'],
},
}
},
corePlugins: { preflight: false },
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(expected)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.invisible {
visibility: hidden;
}
`)
})
})
})
describe('legacy', () => {
test('defaultExtractor', () => {
let config = {
content: {
files: [{ raw: sharedHtml }],
options: {
defaultExtractor: customExtractor,
},
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(expected)
})
})
test('extractors array', () => {
let config = {
content: {
files: [{ raw: sharedHtml }],
options: {
extractors: [
{
extractor: customExtractor,
extensions: ['html'],
},
],
},
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(expected)
})
})
})
+1792 -1809
View File
File diff suppressed because it is too large Load Diff
+48 -50
View File
@@ -1,57 +1,55 @@
import { crosscheck, run, html, css } from './util/run'
import { run, html, css } from './util/run'
crosscheck(() => {
test('custom separator', () => {
let config = {
darkMode: 'selector',
content: [
{
raw: html`
<div class="md_hover_text-right"></div>
<div class="motion-safe_hover_text-center"></div>
<div class="dark_focus_text-left"></div>
<div class="group-hover_focus-within_text-left"></div>
<div class="rtl_active_text-center"></div>
`,
},
],
separator: '_',
}
test('custom separator', () => {
let config = {
darkMode: 'selector',
content: [
{
raw: html`
<div class="md_hover_text-right"></div>
<div class="motion-safe_hover_text-center"></div>
<div class="dark_focus_text-left"></div>
<div class="group-hover_focus-within_text-left"></div>
<div class="rtl_active_text-center"></div>
`,
},
],
separator: '_',
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.group:hover .group-hover_focus-within_text-left:focus-within {
text-align: left;
}
@media (prefers-reduced-motion: no-preference) {
.motion-safe_hover_text-center:hover {
text-align: center;
}
}
@media (min-width: 768px) {
.md_hover_text-right:hover {
text-align: right;
}
}
.rtl_active_text-center:active:where([dir='rtl'], [dir='rtl'] *) {
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.group:hover .group-hover_focus-within_text-left:focus-within {
text-align: left;
}
@media (prefers-reduced-motion: no-preference) {
.motion-safe_hover_text-center:hover {
text-align: center;
}
.dark_focus_text-left:focus:where(.dark, .dark *) {
text-align: left;
}
@media (min-width: 768px) {
.md_hover_text-right:hover {
text-align: right;
}
`)
})
})
test('dash is not supported', () => {
let config = {
darkMode: 'selector',
content: [{ raw: 'lg-hover-font-bold' }],
separator: '-',
}
return expect(run('@tailwind utilities', config)).rejects.toThrowError(
"The '-' character cannot be used as a custom separator in JIT mode due to parsing ambiguity. Please use another character like '_' instead."
)
}
.rtl_active_text-center:active:where([dir='rtl'], [dir='rtl'] *) {
text-align: center;
}
.dark_focus_text-left:focus:where(.dark, .dark *) {
text-align: left;
}
`)
})
})
test('dash is not supported', () => {
let config = {
darkMode: 'selector',
content: [{ raw: 'lg-hover-font-bold' }],
separator: '-',
}
return expect(run('@tailwind utilities', config)).rejects.toThrowError(
"The '-' character cannot be used as a custom separator in JIT mode due to parsing ambiguity. Please use another character like '_' instead."
)
})
+51 -56
View File
@@ -1,72 +1,67 @@
import { crosscheck, run, html, css } from './util/run'
import { run, html, css } from './util/run'
function customTransformer(content) {
return content.replace(/uppercase/g, 'lowercase')
}
crosscheck(({ stable, oxide }) => {
oxide.test.todo('transform function')
stable.test('transform function', () => {
let config = {
content: {
files: [{ raw: html`<div class="uppercase"></div>` }],
transform: customTransformer,
},
}
test('transform function', () => {
let config = {
content: {
files: [{ raw: html`<div class="uppercase"></div>` }],
transform: customTransformer,
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.lowercase {
text-transform: lowercase;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.lowercase {
text-transform: lowercase;
}
`)
})
})
oxide.test.todo('transform.DEFAULT')
stable.test('transform.DEFAULT', () => {
let config = {
content: {
files: [{ raw: html`<div class="uppercase"></div>` }],
transform: {
DEFAULT: customTransformer,
},
test('transform.DEFAULT', () => {
let config = {
content: {
files: [{ raw: html`<div class="uppercase"></div>` }],
transform: {
DEFAULT: customTransformer,
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.lowercase {
text-transform: lowercase;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.lowercase {
text-transform: lowercase;
}
`)
})
})
oxide.test.todo('transform.{extension}')
stable.test('transform.{extension}', () => {
let config = {
content: {
files: [
{ raw: 'blah blah blah', extension: 'html' },
{ raw: 'blah blah blah', extension: 'php' },
],
transform: {
html: () => 'uppercase',
php: () => 'lowercase',
},
test('transform.{extension}', () => {
let config = {
content: {
files: [
{ raw: 'blah blah blah', extension: 'html' },
{ raw: 'blah blah blah', extension: 'php' },
],
transform: {
html: () => 'uppercase',
php: () => 'lowercase',
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.uppercase {
text-transform: uppercase;
}
.lowercase {
text-transform: lowercase;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.uppercase {
text-transform: uppercase;
}
.lowercase {
text-transform: lowercase;
}
`)
})
})
+284 -286
View File
@@ -2,37 +2,100 @@ import fs from 'fs'
import path from 'path'
import inTempDirectory from '../jest/runInTempDirectory'
import { crosscheck, run, html, css, javascript } from './util/run'
import { run, html, css, javascript } from './util/run'
const defaultConfigFile = 'tailwind.config.js'
const cjsConfigFile = 'tailwind.config.cjs'
crosscheck(() => {
test('it uses the values from the custom config file', () => {
let config = require(path.resolve(`${__dirname}/fixtures/custom-config.js`))
test('it uses the values from the custom config file', () => {
let config = require(path.resolve(`${__dirname}/fixtures/custom-config.js`))
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@media (min-width: 400px) {
.mobile\:font-bold {
font-weight: 700;
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@media (min-width: 400px) {
.mobile\:font-bold {
font-weight: 700;
}
`)
})
}
`)
})
})
test('custom config can be passed as an object', () => {
let config = {
test('custom config can be passed as an object', () => {
let config = {
content: [{ raw: html`<div class="mobile:font-bold"></div>` }],
theme: {
screens: {
mobile: '400px',
},
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@media (min-width: 400px) {
.mobile\:font-bold {
font-weight: 700;
}
}
`)
})
})
test('custom config path can be passed using `config` property in an object', () => {
let config = {
config: path.resolve(`${__dirname}/fixtures/custom-config.js`),
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@media (min-width: 400px) {
.mobile\:font-bold {
font-weight: 700;
}
}
`)
})
})
test('custom config can be passed under the `config` property', () => {
let config = {
config: {
content: [{ raw: html`<div class="mobile:font-bold"></div>` }],
theme: {
screens: {
mobile: '400px',
},
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@media (min-width: 400px) {
.mobile\:font-bold {
font-weight: 700;
}
}
`)
})
})
test('tailwind.config.cjs is picked up by default', () => {
return inTempDirectory(() => {
fs.writeFileSync(
path.resolve(cjsConfigFile),
javascript`module.exports = {
content: [{ raw: '<div class="mobile:font-bold"></div>' }],
theme: {
screens: {
mobile: '400px',
},
},
}`
)
return run('@tailwind utilities').then((result) => {
expect(result.css).toMatchFormattedCss(css`
@media (min-width: 400px) {
.mobile\:font-bold {
@@ -42,13 +105,23 @@ crosscheck(() => {
`)
})
})
})
test('custom config path can be passed using `config` property in an object', () => {
let config = {
config: path.resolve(`${__dirname}/fixtures/custom-config.js`),
}
test('tailwind.config.js is picked up by default', () => {
return inTempDirectory(() => {
fs.writeFileSync(
path.resolve(defaultConfigFile),
javascript`module.exports = {
content: [{ raw: '<div class="mobile:font-bold"></div>' }],
theme: {
screens: {
mobile: '400px',
},
},
}`
)
return run('@tailwind utilities', config).then((result) => {
return run('@tailwind utilities').then((result) => {
expect(result.css).toMatchFormattedCss(css`
@media (min-width: 400px) {
.mobile\:font-bold {
@@ -58,20 +131,23 @@ crosscheck(() => {
`)
})
})
})
test('custom config can be passed under the `config` property', () => {
let config = {
config: {
content: [{ raw: html`<div class="mobile:font-bold"></div>` }],
test('tailwind.config.cjs is picked up by default when passing an empty object', () => {
return inTempDirectory(() => {
fs.writeFileSync(
path.resolve(cjsConfigFile),
javascript`module.exports = {
content: [{ raw: '<div class="mobile:font-bold"></div>' }],
theme: {
screens: {
mobile: '400px',
},
},
},
}
}`
)
return run('@tailwind utilities', config).then((result) => {
return run('@tailwind utilities', {}).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@media (min-width: 400px) {
.mobile\:font-bold {
@@ -81,12 +157,13 @@ crosscheck(() => {
`)
})
})
})
test('tailwind.config.cjs is picked up by default', () => {
return inTempDirectory(() => {
fs.writeFileSync(
path.resolve(cjsConfigFile),
javascript`module.exports = {
test('tailwind.config.js is picked up by default when passing an empty object', () => {
return inTempDirectory(() => {
fs.writeFileSync(
path.resolve(defaultConfigFile),
javascript`module.exports = {
content: [{ raw: '<div class="mobile:font-bold"></div>' }],
theme: {
screens: {
@@ -94,297 +171,218 @@ crosscheck(() => {
},
},
}`
)
)
return run('@tailwind utilities').then((result) => {
expect(result.css).toMatchFormattedCss(css`
@media (min-width: 400px) {
.mobile\:font-bold {
font-weight: 700;
}
return run('@tailwind utilities', {}).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@media (min-width: 400px) {
.mobile\:font-bold {
font-weight: 700;
}
`)
})
}
`)
})
})
})
test('tailwind.config.js is picked up by default', () => {
return inTempDirectory(() => {
fs.writeFileSync(
path.resolve(defaultConfigFile),
javascript`module.exports = {
content: [{ raw: '<div class="mobile:font-bold"></div>' }],
test('the default config can be overridden using the presets key', () => {
let config = {
content: [{ raw: html`<div class="min-h-primary min-h-secondary min-h-0"></div>` }],
presets: [
{
theme: {
screens: {
mobile: '400px',
},
extend: { minHeight: { secondary: '24px' } },
},
}`
)
},
],
theme: {
extend: { minHeight: { primary: '48px' } },
},
}
return run('@tailwind utilities').then((result) => {
expect(result.css).toMatchFormattedCss(css`
@media (min-width: 400px) {
.mobile\:font-bold {
font-weight: 700;
}
}
`)
})
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.min-h-0 {
min-height: 0;
}
.min-h-primary {
min-height: 48px;
}
.min-h-secondary {
min-height: 24px;
}
`)
})
})
test('tailwind.config.cjs is picked up by default when passing an empty object', () => {
return inTempDirectory(() => {
fs.writeFileSync(
path.resolve(cjsConfigFile),
javascript`module.exports = {
content: [{ raw: '<div class="mobile:font-bold"></div>' }],
test('presets can be functions', () => {
let config = {
content: [{ raw: html`<div class="min-h-primary min-h-secondary min-h-0"></div>` }],
presets: [
() => ({
theme: {
screens: {
mobile: '400px',
},
extend: { minHeight: { secondary: '24px' } },
},
}`
)
}),
],
theme: {
extend: { minHeight: { primary: '48px' } },
},
}
return run('@tailwind utilities', {}).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@media (min-width: 400px) {
.mobile\:font-bold {
font-weight: 700;
}
}
`)
})
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.min-h-0 {
min-height: 0;
}
.min-h-primary {
min-height: 48px;
}
.min-h-secondary {
min-height: 24px;
}
`)
})
})
test('tailwind.config.js is picked up by default when passing an empty object', () => {
return inTempDirectory(() => {
fs.writeFileSync(
path.resolve(defaultConfigFile),
javascript`module.exports = {
content: [{ raw: '<div class="mobile:font-bold"></div>' }],
test('the default config can be removed by using an empty presets key in a preset', () => {
let config = {
content: [{ raw: html`<div class="min-h-primary min-h-secondary min-h-0"></div>` }],
presets: [
{
presets: [],
theme: {
screens: {
mobile: '400px',
},
},
}`
)
return run('@tailwind utilities', {}).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@media (min-width: 400px) {
.mobile\:font-bold {
font-weight: 700;
}
}
`)
})
})
})
test('the default config can be overridden using the presets key', () => {
let config = {
content: [{ raw: html`<div class="min-h-primary min-h-secondary min-h-0"></div>` }],
presets: [
{
theme: {
extend: { minHeight: { secondary: '24px' } },
},
extend: { minHeight: { secondary: '24px' } },
},
],
theme: {
extend: { minHeight: { primary: '48px' } },
},
}
],
theme: {
extend: { minHeight: { primary: '48px' } },
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.min-h-0 {
min-height: 0;
}
.min-h-primary {
min-height: 48px;
}
.min-h-secondary {
min-height: 24px;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.min-h-primary {
min-height: 48px;
}
.min-h-secondary {
min-height: 24px;
}
`)
})
})
test('presets can be functions', () => {
let config = {
content: [{ raw: html`<div class="min-h-primary min-h-secondary min-h-0"></div>` }],
presets: [
() => ({
theme: {
extend: { minHeight: { secondary: '24px' } },
},
}),
],
theme: {
extend: { minHeight: { primary: '48px' } },
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.min-h-0 {
min-height: 0;
}
.min-h-primary {
min-height: 48px;
}
.min-h-secondary {
min-height: 24px;
}
`)
})
})
test('the default config can be removed by using an empty presets key in a preset', () => {
let config = {
content: [{ raw: html`<div class="min-h-primary min-h-secondary min-h-0"></div>` }],
presets: [
{
presets: [],
theme: {
extend: { minHeight: { secondary: '24px' } },
},
test('presets can have their own presets', () => {
let config = {
content: [{ raw: html`<div class="bg-red bg-transparent bg-black bg-white"></div>` }],
presets: [
{
presets: [],
theme: {
colors: { red: '#dd0000' },
},
],
theme: {
extend: { minHeight: { primary: '48px' } },
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.min-h-primary {
min-height: 48px;
}
.min-h-secondary {
min-height: 24px;
}
`)
})
})
test('presets can have their own presets', () => {
let config = {
content: [{ raw: html`<div class="bg-red bg-transparent bg-black bg-white"></div>` }],
presets: [
{
presets: [],
theme: {
colors: { red: '#dd0000' },
},
},
{
presets: [
{
presets: [],
theme: {
colors: {
transparent: 'transparent',
red: '#ff0000',
},
{
presets: [
{
presets: [],
theme: {
colors: {
transparent: 'transparent',
red: '#ff0000',
},
},
],
theme: {
extend: {
colors: {
black: 'black',
red: '#ee0000',
},
backgroundColor: (theme) => theme('colors'),
},
],
theme: {
extend: {
colors: {
black: 'black',
red: '#ee0000',
},
backgroundColor: (theme) => theme('colors'),
},
corePlugins: ['backgroundColor'],
},
],
theme: {
extend: { colors: { white: 'white' } },
corePlugins: ['backgroundColor'],
},
}
],
theme: {
extend: { colors: { white: 'white' } },
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.bg-black {
background-color: #000;
}
.bg-red {
background-color: #e00;
}
.bg-transparent {
background-color: #0000;
}
.bg-white {
background-color: #fff;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.bg-black {
background-color: #000;
}
.bg-red {
background-color: #e00;
}
.bg-transparent {
background-color: #0000;
}
.bg-white {
background-color: #fff;
}
`)
})
})
test('function presets can be mixed with object presets', () => {
let config = {
content: [{ raw: html`<div class="bg-red bg-transparent bg-black bg-white"></div>` }],
presets: [
() => ({
presets: [],
theme: {
colors: { red: '#dd0000' },
},
}),
{
presets: [
() => ({
presets: [],
theme: {
colors: {
transparent: 'transparent',
red: '#ff0000',
},
},
}),
],
theme: {
extend: {
test('function presets can be mixed with object presets', () => {
let config = {
content: [{ raw: html`<div class="bg-red bg-transparent bg-black bg-white"></div>` }],
presets: [
() => ({
presets: [],
theme: {
colors: { red: '#dd0000' },
},
}),
{
presets: [
() => ({
presets: [],
theme: {
colors: {
black: 'black',
red: '#ee0000',
transparent: 'transparent',
red: '#ff0000',
},
backgroundColor: (theme) => theme('colors'),
},
}),
],
theme: {
extend: {
colors: {
black: 'black',
red: '#ee0000',
},
backgroundColor: (theme) => theme('colors'),
},
corePlugins: ['backgroundColor'],
},
],
theme: {
extend: { colors: { white: 'white' } },
corePlugins: ['backgroundColor'],
},
}
],
theme: {
extend: { colors: { white: 'white' } },
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.bg-black {
background-color: #000;
}
.bg-red {
background-color: #e00;
}
.bg-transparent {
background-color: #0000;
}
.bg-white {
background-color: #fff;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.bg-black {
background-color: #000;
}
.bg-red {
background-color: #e00;
}
.bg-transparent {
background-color: #0000;
}
.bg-white {
background-color: #fff;
}
`)
})
})
+270 -298
View File
@@ -1,321 +1,293 @@
import { crosscheck, run, html, css, defaults } from './util/run'
import { run, html, css, defaults } from './util/run'
crosscheck(({ oxide, stable }) => {
it('should be possible to use the darkMode "class" mode', () => {
let config = {
darkMode: 'class',
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
corePlugins: { preflight: false },
}
it('should be possible to use the darkMode "class" mode', () => {
let config = {
darkMode: 'class',
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
:is(.dark .dark\:font-bold) {
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
:is(.dark .dark\:font-bold) {
font-weight: 700;
}
`)
})
})
it('should be possible to change the class name', () => {
let config = {
darkMode: ['class', '.test-dark'],
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
:is(.test-dark .dark\:font-bold) {
font-weight: 700;
}
`)
})
})
it('should be possible to use the darkMode "media" mode', () => {
let config = {
darkMode: 'media',
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
@media (prefers-color-scheme: dark) {
.dark\:font-bold {
font-weight: 700;
}
`)
})
}
`)
})
it('should be possible to change the class name', () => {
let config = {
darkMode: ['class', '.test-dark'],
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
:is(.test-dark .dark\:font-bold) {
})
it('should default to the `media` mode when no mode is provided', () => {
let config = {
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
@media (prefers-color-scheme: dark) {
.dark\:font-bold {
font-weight: 700;
}
`)
})
}
`)
})
it('should be possible to use the darkMode "media" mode', () => {
let config = {
darkMode: 'media',
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
@media (prefers-color-scheme: dark) {
.dark\:font-bold {
font-weight: 700;
}
})
it('should default to the `media` mode when mode is set to `false`', () => {
let config = {
darkMode: false,
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
@media (prefers-color-scheme: dark) {
.dark\:font-bold {
font-weight: 700;
}
`)
})
}
`)
})
it('should default to the `media` mode when no mode is provided', () => {
let config = {
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
@media (prefers-color-scheme: dark) {
.dark\:font-bold {
font-weight: 700;
}
}
`)
})
})
it('should support the deprecated `class` dark mode behavior', () => {
let config = {
darkMode: 'class',
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
:is(.dark .dark\:font-bold) {
font-weight: 700;
}
`)
})
it('should default to the `media` mode when mode is set to `false`', () => {
let config = {
darkMode: false,
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
@media (prefers-color-scheme: dark) {
.dark\:font-bold {
font-weight: 700;
}
}
`)
})
})
it('should support custom classes with deprecated `class` dark mode', () => {
let config = {
darkMode: ['class', '.my-dark'],
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
:is(.my-dark .dark\:font-bold) {
font-weight: 700;
}
`)
})
it('should support the deprecated `class` dark mode behavior', () => {
let config = {
darkMode: 'class',
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
:is(.dark .dark\:font-bold) {
font-weight: 700;
}
`)
})
})
it('should support custom classes with deprecated `class` dark mode', () => {
let config = {
darkMode: ['class', '.my-dark'],
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
:is(.my-dark .dark\:font-bold) {
font-weight: 700;
}
`)
})
})
it('should use legacy sorting when using `darkMode: class`', () => {
let config = {
darkMode: 'class',
content: [
{
raw: html`<div class="dark:text-green-100 hover:text-green-200 lg:text-green-300"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind utilities;
`
return run(input, config).then((result) => {
stable.expect(result.css).toMatchFormattedCss(css`
.hover\:text-green-200:hover {
--tw-text-opacity: 1;
color: rgb(187 247 208 / var(--tw-text-opacity));
}
:is(.dark .dark\:text-green-100) {
})
it('should use legacy sorting when using `darkMode: class`', () => {
let config = {
darkMode: 'class',
content: [
{
raw: html`<div class="dark:text-green-100 hover:text-green-200 lg:text-green-300"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.hover\:text-green-200:hover {
--tw-text-opacity: 1;
color: rgb(187 247 208 / var(--tw-text-opacity));
}
:is(.dark .dark\:text-green-100) {
--tw-text-opacity: 1;
color: rgb(220 252 231 / var(--tw-text-opacity));
}
@media (min-width: 1024px) {
.lg\:text-green-300 {
--tw-text-opacity: 1;
color: rgb(220 252 231 / var(--tw-text-opacity));
}
@media (min-width: 1024px) {
.lg\:text-green-300 {
--tw-text-opacity: 1;
color: rgb(134 239 172 / var(--tw-text-opacity));
}
}
`)
oxide.expect(result.css).toMatchFormattedCss(css`
.hover\:text-green-200:hover {
color: #bbf7d0;
color: rgb(134 239 172 / var(--tw-text-opacity));
}
:is(.dark .dark\:text-green-100) {
color: #dcfce7;
}
@media (min-width: 1024px) {
.lg\:text-green-300 {
color: #86efac;
}
}
`)
})
}
`)
})
it('should use modern sorting otherwise', () => {
let config = {
darkMode: 'selector',
content: [
{
raw: html`<div class="dark:text-green-100 hover:text-green-200 lg:text-green-300"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind utilities;
`
return run(input, config).then((result) => {
stable.expect(result.css).toMatchFormattedCss(css`
.hover\:text-green-200:hover {
})
it('should use modern sorting otherwise', () => {
let config = {
darkMode: 'selector',
content: [
{
raw: html`<div class="dark:text-green-100 hover:text-green-200 lg:text-green-300"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.hover\:text-green-200:hover {
--tw-text-opacity: 1;
color: rgb(187 247 208 / var(--tw-text-opacity));
}
@media (min-width: 1024px) {
.lg\:text-green-300 {
--tw-text-opacity: 1;
color: rgb(187 247 208 / var(--tw-text-opacity));
}
@media (min-width: 1024px) {
.lg\:text-green-300 {
--tw-text-opacity: 1;
color: rgb(134 239 172 / var(--tw-text-opacity));
}
}
.dark\:text-green-100:where(.dark, .dark *) {
--tw-text-opacity: 1;
color: rgb(220 252 231 / var(--tw-text-opacity));
}
`)
oxide.expect(result.css).toMatchFormattedCss(css`
.hover\:text-green-200:hover {
color: #bbf7d0;
}
@media (min-width: 1024px) {
.lg\:text-green-300 {
color: #86efac;
}
}
.dark\:text-green-100:where(.dark, .dark *) {
color: #dcfce7;
color: rgb(134 239 172 / var(--tw-text-opacity));
}
`)
})
}
.dark\:text-green-100:where(.dark, .dark *) {
--tw-text-opacity: 1;
color: rgb(220 252 231 / var(--tw-text-opacity));
}
`)
})
it('should allow customization of the dark mode variant', () => {
let config = {
darkMode: ['variant', '&:not(.light *)'],
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.dark\:font-bold:not(.light *) {
font-weight: 700;
}
`)
})
})
it('should allow customization of the dark mode variant', () => {
let config = {
darkMode: ['variant', '&:not(.light *)'],
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.dark\:font-bold:not(.light *) {
font-weight: 700;
}
`)
})
it('should support parallel selectors for the dark mode variant', () => {
let config = {
darkMode: ['variant', ['&:not(.light *)', '&:not(.extralight *)']],
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.dark\:font-bold:not(.light *),
.dark\:font-bold:not(.extralight *) {
font-weight: 700;
}
`)
})
})
it('should support parallel selectors for the dark mode variant', () => {
let config = {
darkMode: ['variant', ['&:not(.light *)', '&:not(.extralight *)']],
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.dark\:font-bold:not(.light *),
.dark\:font-bold:not(.extralight *) {
font-weight: 700;
}
`)
})
it('should support fn selectors for the dark mode variant', () => {
let config = {
darkMode: ['variant', () => ['&:not(.light *)', '&:not(.extralight *)']],
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.dark\:font-bold:not(.light *),
.dark\:font-bold:not(.extralight *) {
font-weight: 700;
}
`)
})
})
it('should support fn selectors for the dark mode variant', () => {
let config = {
darkMode: ['variant', () => ['&:not(.light *)', '&:not(.extralight *)']],
content: [{ raw: html`<div class="dark:font-bold"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.dark\:font-bold:not(.light *),
.dark\:font-bold:not(.extralight *) {
font-weight: 700;
}
`)
})
})
-2
View File
@@ -1,8 +1,6 @@
import config from '../src/public/default-config'
import configStub from '../stubs/config.full'
test.todo('remove mutation from these tests so we can run against both engines')
test('the default config matches the stub', () => {
expect(config).toEqual(configStub)
})
-2
View File
@@ -1,8 +1,6 @@
import theme from '../src/public/default-theme'
import configStub from '../stubs/config.full.js'
test.todo('remove mutation from these tests so we can run against both engines')
test('the default theme matches the stub', () => {
expect(theme).toEqual(configStub.theme)
})
+104 -106
View File
@@ -1,130 +1,128 @@
import { crosscheck, run, html, css } from './util/run'
import { run, html, css } from './util/run'
crosscheck(() => {
it('should warn when we detect nested css', () => {
let config = {
content: [{ raw: html`<div class="nested"></div>` }],
}
it('should warn when we detect nested css', () => {
let config = {
content: [{ raw: html`<div class="nested"></div>` }],
}
let input = css`
@tailwind utilities;
let input = css`
@tailwind utilities;
.nested {
.example {
}
.nested {
.example {
}
`
return run(input, config).then((result) => {
expect(result.messages).toHaveLength(1)
expect(result.messages).toMatchObject([
{
type: 'warning',
text: [
'Nested CSS was detected, but CSS nesting has not been configured correctly.',
'Please enable a CSS nesting plugin *before* Tailwind in your configuration.',
'See how here: https://tailwindcss.com/docs/using-with-preprocessors#nesting',
].join('\n'),
},
])
})
})
it('should not warn when we detect nested css inside css @layer rules', () => {
let config = {
content: [{ raw: html`<div class="underline"></div>` }],
}
`
let input = css`
return run(input, config).then((result) => {
expect(result.messages).toHaveLength(1)
expect(result.messages).toMatchObject([
{
type: 'warning',
text: [
'Nested CSS was detected, but CSS nesting has not been configured correctly.',
'Please enable a CSS nesting plugin *before* Tailwind in your configuration.',
'See how here: https://tailwindcss.com/docs/using-with-preprocessors#nesting',
].join('\n'),
},
])
})
})
it('should not warn when we detect nested css inside css @layer rules', () => {
let config = {
content: [{ raw: html`<div class="underline"></div>` }],
}
let input = css`
@layer tw-base, tw-components, tw-utilities;
@layer tw-utilities {
@tailwind utilities;
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@layer tw-base, tw-components, tw-utilities;
@layer tw-utilities {
@tailwind utilities;
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@layer tw-base, tw-components, tw-utilities;
@layer tw-utilities {
.underline {
text-decoration-line: underline;
}
}
`)
expect(result.messages).toHaveLength(0)
})
})
it('should warn when we detect namespaced @tailwind at rules', () => {
let config = {
content: [{ raw: html`<div class="text-center"></div>` }],
}
let input = css`
.namespace {
@tailwind utilities;
}
`
return run(input, config).then((result) => {
expect(result.messages).toHaveLength(1)
expect(result.messages).toMatchObject([
{
type: 'warning',
text: [
'Nested @tailwind rules were detected, but are not supported.',
"Consider using a prefix to scope Tailwind's classes: https://tailwindcss.com/docs/configuration#prefix",
'Alternatively, use the important selector strategy: https://tailwindcss.com/docs/configuration#selector-strategy',
].join('\n'),
},
])
})
})
it('should not warn when nesting a single rule inside a media query', () => {
let config = {
content: [{ raw: html`<div class="nested"></div>` }],
}
let input = css`
@tailwind utilities;
@media (min-width: 768px) {
.nested {
.underline {
text-decoration-line: underline;
}
}
`
return run(input, config).then((result) => {
expect(result.messages).toHaveLength(0)
expect(result.messages).toEqual([])
})
`)
expect(result.messages).toHaveLength(0)
})
})
it('should only warn for the first detected nesting ', () => {
let config = {
content: [{ raw: html`<div class="nested other"></div>` }],
}
it('should warn when we detect namespaced @tailwind at rules', () => {
let config = {
content: [{ raw: html`<div class="text-center"></div>` }],
}
let input = css`
let input = css`
.namespace {
@tailwind utilities;
}
`
return run(input, config).then((result) => {
expect(result.messages).toHaveLength(1)
expect(result.messages).toMatchObject([
{
type: 'warning',
text: [
'Nested @tailwind rules were detected, but are not supported.',
"Consider using a prefix to scope Tailwind's classes: https://tailwindcss.com/docs/configuration#prefix",
'Alternatively, use the important selector strategy: https://tailwindcss.com/docs/configuration#selector-strategy',
].join('\n'),
},
])
})
})
it('should not warn when nesting a single rule inside a media query', () => {
let config = {
content: [{ raw: html`<div class="nested"></div>` }],
}
let input = css`
@tailwind utilities;
@media (min-width: 768px) {
.nested {
.example {
}
}
}
`
.other {
}
return run(input, config).then((result) => {
expect(result.messages).toHaveLength(0)
expect(result.messages).toEqual([])
})
})
it('should only warn for the first detected nesting ', () => {
let config = {
content: [{ raw: html`<div class="nested other"></div>` }],
}
let input = css`
@tailwind utilities;
.nested {
.example {
}
.other {
.example {
}
}
`
}
return run(input, config).then((result) => {
expect(result.messages).toHaveLength(1)
})
.other {
.example {
}
}
`
return run(input, config).then((result) => {
expect(result.messages).toHaveLength(1)
})
})
+2 -5
View File
@@ -1,8 +1,5 @@
import escapeClassName from '../src/util/escapeClassName'
import { crosscheck } from './util/run'
crosscheck(() => {
test('invalid characters are escaped', () => {
expect(escapeClassName('w:_$-1/2')).toEqual('w\\:_\\$-1\\/2')
})
test('invalid characters are escaped', () => {
expect(escapeClassName('w:_$-1/2')).toEqual('w\\:_\\$-1\\/2')
})
File diff suppressed because it is too large Load Diff
+170 -172
View File
@@ -1,197 +1,195 @@
import { crosscheck, run, html, css } from './util/run'
import { run, html, css } from './util/run'
crosscheck(() => {
test('experimental universal selector improvements (box-shadow)', () => {
let config = {
experimental: 'all',
content: [{ raw: html`<div class="resize shadow"></div>` }],
corePlugins: { preflight: false },
}
test('experimental universal selector improvements (box-shadow)', () => {
let config = {
experimental: 'all',
content: [{ raw: html`<div class="resize shadow"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind utilities;
`
let input = css`
@tailwind base;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchCss(css`
.shadow {
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
}
.resize {
resize: both;
}
.shadow {
--tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
0 1px 2px -1px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
`)
})
return run(input, config).then((result) => {
expect(result.css).toMatchCss(css`
.shadow {
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
}
.resize {
resize: both;
}
.shadow {
--tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
0 1px 2px -1px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
`)
})
})
test('experimental universal selector improvements (pseudo hover)', () => {
let config = {
experimental: 'all',
content: [{ raw: html`<div class="resize hover:shadow"></div>` }],
corePlugins: { preflight: false },
}
test('experimental universal selector improvements (pseudo hover)', () => {
let config = {
experimental: 'all',
content: [{ raw: html`<div class="resize hover:shadow"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind utilities;
`
let input = css`
@tailwind base;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchCss(css`
.hover\:shadow {
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
}
.resize {
resize: both;
}
.hover\:shadow:hover {
--tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
0 1px 2px -1px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
`)
})
return run(input, config).then((result) => {
expect(result.css).toMatchCss(css`
.hover\:shadow {
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
}
.resize {
resize: both;
}
.hover\:shadow:hover {
--tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
0 1px 2px -1px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
`)
})
})
test('experimental universal selector improvements (multiple classes: group)', () => {
let config = {
experimental: 'all',
content: [{ raw: html`<div class="resize group-hover:shadow"></div>` }],
corePlugins: { preflight: false },
}
test('experimental universal selector improvements (multiple classes: group)', () => {
let config = {
experimental: 'all',
content: [{ raw: html`<div class="resize group-hover:shadow"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind utilities;
`
let input = css`
@tailwind base;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchCss(css`
.group-hover\:shadow {
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
}
.resize {
resize: both;
}
.group:hover .group-hover\:shadow {
--tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
0 1px 2px -1px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
`)
})
return run(input, config).then((result) => {
expect(result.css).toMatchCss(css`
.group-hover\:shadow {
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
}
.resize {
resize: both;
}
.group:hover .group-hover\:shadow {
--tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
0 1px 2px -1px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
`)
})
})
test('experimental universal selector improvements (child selectors: divide-y)', () => {
let config = {
experimental: 'all',
content: [{ raw: html`<div class="resize divide-y"></div>` }],
corePlugins: { preflight: false },
}
test('experimental universal selector improvements (child selectors: divide-y)', () => {
let config = {
experimental: 'all',
content: [{ raw: html`<div class="resize divide-y"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind utilities;
`
let input = css`
@tailwind base;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchCss(css`
.resize {
resize: both;
}
.divide-y > :not([hidden]) ~ :not([hidden]) {
--tw-divide-y-reverse: 0;
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
}
`)
})
return run(input, config).then((result) => {
expect(result.css).toMatchCss(css`
.resize {
resize: both;
}
.divide-y > :not([hidden]) ~ :not([hidden]) {
--tw-divide-y-reverse: 0;
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
}
`)
})
})
test('experimental universal selector improvements (hover:divide-y)', () => {
let config = {
experimental: 'all',
content: [{ raw: html`<div class="resize hover:divide-y"></div>` }],
corePlugins: { preflight: false },
}
test('experimental universal selector improvements (hover:divide-y)', () => {
let config = {
experimental: 'all',
content: [{ raw: html`<div class="resize hover:divide-y"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind utilities;
`
let input = css`
@tailwind base;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchCss(css`
.resize {
resize: both;
}
.hover\:divide-y:hover > :not([hidden]) ~ :not([hidden]) {
--tw-divide-y-reverse: 0;
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
}
`)
})
return run(input, config).then((result) => {
expect(result.css).toMatchCss(css`
.resize {
resize: both;
}
.hover\:divide-y:hover > :not([hidden]) ~ :not([hidden]) {
--tw-divide-y-reverse: 0;
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
}
`)
})
})
test('experimental universal selector improvements (#app important)', () => {
let config = {
experimental: 'all',
important: '#app',
content: [{ raw: html`<div class="resize divide-y shadow"></div>` }],
corePlugins: { preflight: false },
}
test('experimental universal selector improvements (#app important)', () => {
let config = {
experimental: 'all',
important: '#app',
content: [{ raw: html`<div class="resize divide-y shadow"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind utilities;
`
let input = css`
@tailwind base;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchCss(css`
.shadow {
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
}
#app :is(.resize) {
resize: both;
}
#app :is(.divide-y > :not([hidden]) ~ :not([hidden])) {
--tw-divide-y-reverse: 0;
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
}
#app :is(.shadow) {
--tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
0 1px 2px -1px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
`)
})
return run(input, config).then((result) => {
expect(result.css).toMatchCss(css`
.shadow {
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
}
#app :is(.resize) {
resize: both;
}
#app :is(.divide-y > :not([hidden]) ~ :not([hidden])) {
--tw-divide-y-reverse: 0;
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
}
#app :is(.shadow) {
--tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
0 1px 2px -1px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
`)
})
})
+25 -27
View File
@@ -1,34 +1,32 @@
import { crosscheck, run, html, css } from './util/run'
import { run, html, css } from './util/run'
crosscheck(() => {
test('PHP arrays', async () => {
let config = {
content: [
{
raw: html`<h1 class="<?php echo wrap(['class' => "max-w-[16rem]"]); ?>">Hello world</h1>`,
},
],
}
test('PHP arrays', async () => {
let config = {
content: [
{
raw: html`<h1 class="<?php echo wrap(['class' => "max-w-[16rem]"]); ?>">Hello world</h1>`,
},
],
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.max-w-\[16rem\] {
max-width: 16rem;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.max-w-\[16rem\] {
max-width: 16rem;
}
`)
})
})
test('arbitrary values with quotes', async () => {
let config = { content: [{ raw: html`<div class="content-['hello]']"></div>` }] }
test('arbitrary values with quotes', async () => {
let config = { content: [{ raw: html`<div class="content-['hello]']"></div>` }] }
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.content-\[\'hello\]\'\] {
--tw-content: 'hello]';
content: var(--tw-content);
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.content-\[\'hello\]\'\] {
--tw-content: 'hello]';
content: var(--tw-content);
}
`)
})
})
+75 -78
View File
@@ -1,84 +1,81 @@
import { crosscheck } from './util/run'
import flattenColorPalette from '../src/util/flattenColorPalette'
crosscheck(() => {
test('it flattens nested color objects', () => {
expect(
flattenColorPalette({
purple: 'purple',
white: {
25: 'rgba(255,255,255,.25)',
50: 'rgba(255,255,255,.5)',
75: 'rgba(255,255,255,.75)',
DEFAULT: '#fff',
},
red: {
1: 'rgb(33,0,0)',
2: 'rgb(67,0,0)',
3: 'rgb(100,0,0)',
},
green: {
1: 'rgb(0,33,0)',
2: 'rgb(0,67,0)',
3: 'rgb(0,100,0)',
},
blue: {
1: 'rgb(0,0,33)',
2: 'rgb(0,0,67)',
3: 'rgb(0,0,100)',
},
})
).toEqual({
test('it flattens nested color objects', () => {
expect(
flattenColorPalette({
purple: 'purple',
'white-25': 'rgba(255,255,255,.25)',
'white-50': 'rgba(255,255,255,.5)',
'white-75': 'rgba(255,255,255,.75)',
white: '#fff',
'red-1': 'rgb(33,0,0)',
'red-2': 'rgb(67,0,0)',
'red-3': 'rgb(100,0,0)',
'green-1': 'rgb(0,33,0)',
'green-2': 'rgb(0,67,0)',
'green-3': 'rgb(0,100,0)',
'blue-1': 'rgb(0,0,33)',
'blue-2': 'rgb(0,0,67)',
'blue-3': 'rgb(0,0,100)',
white: {
25: 'rgba(255,255,255,.25)',
50: 'rgba(255,255,255,.5)',
75: 'rgba(255,255,255,.75)',
DEFAULT: '#fff',
},
red: {
1: 'rgb(33,0,0)',
2: 'rgb(67,0,0)',
3: 'rgb(100,0,0)',
},
green: {
1: 'rgb(0,33,0)',
2: 'rgb(0,67,0)',
3: 'rgb(0,100,0)',
},
blue: {
1: 'rgb(0,0,33)',
2: 'rgb(0,0,67)',
3: 'rgb(0,0,100)',
},
})
})
test('it flattens deeply nested color objects', () => {
expect(
flattenColorPalette({
primary: 'purple',
secondary: {
DEFAULT: 'blue',
hover: 'cyan',
focus: 'red',
},
button: {
primary: {
DEFAULT: 'magenta',
hover: 'green',
focus: {
DEFAULT: 'yellow',
variant: 'orange',
},
},
},
})
).toEqual({
primary: 'purple',
secondary: 'blue',
'secondary-hover': 'cyan',
'secondary-focus': 'red',
'button-primary': 'magenta',
'button-primary-hover': 'green',
'button-primary-focus': 'yellow',
'button-primary-focus-variant': 'orange',
})
})
test('it handles empty objects', () => {
expect(flattenColorPalette({})).toEqual({})
).toEqual({
purple: 'purple',
'white-25': 'rgba(255,255,255,.25)',
'white-50': 'rgba(255,255,255,.5)',
'white-75': 'rgba(255,255,255,.75)',
white: '#fff',
'red-1': 'rgb(33,0,0)',
'red-2': 'rgb(67,0,0)',
'red-3': 'rgb(100,0,0)',
'green-1': 'rgb(0,33,0)',
'green-2': 'rgb(0,67,0)',
'green-3': 'rgb(0,100,0)',
'blue-1': 'rgb(0,0,33)',
'blue-2': 'rgb(0,0,67)',
'blue-3': 'rgb(0,0,100)',
})
})
test('it flattens deeply nested color objects', () => {
expect(
flattenColorPalette({
primary: 'purple',
secondary: {
DEFAULT: 'blue',
hover: 'cyan',
focus: 'red',
},
button: {
primary: {
DEFAULT: 'magenta',
hover: 'green',
focus: {
DEFAULT: 'yellow',
variant: 'orange',
},
},
},
})
).toEqual({
primary: 'purple',
secondary: 'blue',
'secondary-hover': 'cyan',
'secondary-focus': 'red',
'button-primary': 'magenta',
'button-primary-hover': 'green',
'button-primary-focus': 'yellow',
'button-primary-focus-variant': 'orange',
})
})
test('it handles empty objects', () => {
expect(flattenColorPalette({})).toEqual({})
})
+312 -318
View File
@@ -1,367 +1,361 @@
import { finalizeSelector } from '../src/util/formatVariantSelector'
import { crosscheck } from './util/run'
crosscheck(() => {
it('should be possible to add a simple variant to a simple selector', () => {
let selector = '.text-center'
let candidate = 'hover:text-center'
it('should be possible to add a simple variant to a simple selector', () => {
let selector = '.text-center'
let candidate = 'hover:text-center'
let formats = [{ format: '&:hover', respectPrefix: true }]
expect(finalizeSelector(selector, formats, { candidate })).toEqual('.hover\\:text-center:hover')
})
it('should be possible to add a multiple simple variants to a simple selector', () => {
let selector = '.text-center'
let candidate = 'focus:hover:text-center'
let formats = [
{ format: '&:hover', respectPrefix: true },
{ format: '&:focus', respectPrefix: true },
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.focus\\:hover\\:text-center:hover:focus'
)
})
it('should be possible to add a simple variant to a selector containing escaped parts', () => {
let selector = '.bg-\\[rgba\\(0\\,0\\,0\\)\\]'
let candidate = 'hover:bg-[rgba(0,0,0)]'
let formats = [{ format: '&:hover', respectPrefix: true }]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.hover\\:bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]:hover'
)
})
it('should be possible to add a simple variant to a selector containing escaped parts (escape is slightly different)', () => {
let selector = '.bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]'
let candidate = 'hover:bg-[rgba(0,0,0)]'
let formats = [{ format: '&:hover', respectPrefix: true }]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.hover\\:bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]:hover'
)
})
it('should be possible to add a simple variant to a more complex selector', () => {
let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
let candidate = 'hover:space-x-4'
let formats = [{ format: '&:hover', respectPrefix: true }]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.hover\\:space-x-4:hover > :not([hidden]) ~ :not([hidden])'
)
})
it('should be possible to add multiple simple variants to a more complex selector', () => {
let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
let candidate = 'disabled:focus:hover:space-x-4'
let formats = [
{ format: '&:hover', respectPrefix: true },
{ format: '&:focus', respectPrefix: true },
{ format: '&:disabled', respectPrefix: true },
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.disabled\\:focus\\:hover\\:space-x-4:hover:focus:disabled > :not([hidden]) ~ :not([hidden])'
)
})
it('should be possible to add a single merge variant to a simple selector', () => {
let selector = '.text-center'
let candidate = 'group-hover:text-center'
let formats = [{ format: ':merge(.group):hover &', respectPrefix: true }]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.group:hover .group-hover\\:text-center'
)
})
it('should be possible to add multiple merge variants to a simple selector', () => {
let selector = '.text-center'
let candidate = 'group-focus:group-hover:text-center'
let formats = [
{ format: ':merge(.group):hover &', respectPrefix: true },
{ format: ':merge(.group):focus &', respectPrefix: true },
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.group:focus:hover .group-focus\\:group-hover\\:text-center'
)
})
it('should be possible to add a single merge variant to a more complex selector', () => {
let selector = '.space-x-4 ~ :not([hidden]) ~ :not([hidden])'
let candidate = 'group-hover:space-x-4'
let formats = [{ format: ':merge(.group):hover &', respectPrefix: true }]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.group:hover .group-hover\\:space-x-4 ~ :not([hidden]) ~ :not([hidden])'
)
})
it('should be possible to add multiple merge variants to a more complex selector', () => {
let selector = '.space-x-4 ~ :not([hidden]) ~ :not([hidden])'
let candidate = 'group-focus:group-hover:space-x-4'
let formats = [
{ format: ':merge(.group):hover &', respectPrefix: true },
{ format: ':merge(.group):focus &', respectPrefix: true },
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.group:focus:hover .group-focus\\:group-hover\\:space-x-4 ~ :not([hidden]) ~ :not([hidden])'
)
})
it('should be possible to add multiple unique merge variants to a simple selector', () => {
let selector = '.text-center'
let candidate = 'peer-focus:group-hover:text-center'
let formats = [
{ format: ':merge(.group):hover &', respectPrefix: true },
{ format: ':merge(.peer):focus ~ &' },
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.peer:focus ~ .group:hover .peer-focus\\:group-hover\\:text-center'
)
})
it('should be possible to add multiple unique merge variants to a simple selector', () => {
let selector = '.text-center'
let candidate = 'group-hover:peer-focus:text-center'
let formats = [
{ format: ':merge(.peer):focus ~ &', respectPrefix: true },
{ format: ':merge(.group):hover &', respectPrefix: true },
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.group:hover .peer:focus ~ .group-hover\\:peer-focus\\:text-center'
)
})
it('should be possible to use multiple :merge() calls with different "arguments"', () => {
let selector = '.foo'
let candidate = 'peer-focus:group-focus:peer-hover:group-hover:foo'
let formats = [
{ format: ':merge(.group):hover &', respectPrefix: true },
{ format: ':merge(.peer):hover ~ &', respectPrefix: true },
{ format: ':merge(.group):focus &', respectPrefix: true },
{ format: ':merge(.peer):focus ~ &', respectPrefix: true },
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.peer:focus:hover ~ .group:focus:hover .peer-focus\\:group-focus\\:peer-hover\\:group-hover\\:foo'
)
})
it('group hover and prose headings combination', () => {
let selector = '.text-center'
let candidate = 'group-hover:prose-headings:text-center'
let formats = [
{ format: ':where(&) :is(h1, h2, h3, h4)', respectPrefix: true }, // Prose Headings
{ format: ':merge(.group):hover &', respectPrefix: true }, // Group Hover
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.group:hover :where(.group-hover\\:prose-headings\\:text-center) :is(h1, h2, h3, h4)'
)
})
it('group hover and prose headings combination flipped', () => {
let selector = '.text-center'
let candidate = 'prose-headings:group-hover:text-center'
let formats = [
{ format: ':merge(.group):hover &', respectPrefix: true }, // Group Hover
{ format: ':where(&) :is(h1, h2, h3, h4)', respectPrefix: true }, // Prose Headings
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
':where(.group:hover .prose-headings\\:group-hover\\:text-center) :is(h1, h2, h3, h4)'
)
})
it('should be possible to handle a complex utility', () => {
let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
let candidate = 'peer-disabled:peer-first-child:group-hover:group-focus:focus:hover:space-x-4'
let formats = [
{ format: '&:hover', respectPrefix: true }, // Hover
{ format: '&:focus', respectPrefix: true }, // Focus
{ format: ':merge(.group):focus &', respectPrefix: true }, // Group focus
{ format: ':merge(.group):hover &', respectPrefix: true }, // Group hover
{ format: ':merge(.peer):first-child ~ &', respectPrefix: true }, // Peer first-child
{ format: ':merge(.peer):disabled ~ &', respectPrefix: true }, // Peer disabled
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.peer:disabled:first-child ~ .group:hover:focus .peer-disabled\\:peer-first-child\\:group-hover\\:group-focus\\:focus\\:hover\\:space-x-4:hover:focus > :not([hidden]) ~ :not([hidden])'
)
})
it('should match base utilities that are prefixed', () => {
let context = { tailwindConfig: { prefix: 'tw-' } }
let selector = '.tw-text-center'
let candidate = 'tw-text-center'
let formats = []
expect(finalizeSelector(selector, formats, { candidate, context })).toEqual('.tw-text-center')
})
it('should prefix classes from variants', () => {
let context = { tailwindConfig: { prefix: 'tw-' } }
let selector = '.tw-text-center'
let candidate = 'foo:tw-text-center'
let formats = [{ format: '.foo &', respectPrefix: true }]
expect(finalizeSelector(selector, formats, { candidate, context })).toEqual(
'.tw-foo .foo\\:tw-text-center'
)
})
it('should not prefix classes from arbitrary variants', () => {
let context = { tailwindConfig: { prefix: 'tw-' } }
let selector = '.tw-text-center'
let candidate = '[.foo_&]:tw-text-center'
let formats = [{ format: '.foo &', respectPrefix: false }]
expect(finalizeSelector(selector, formats, { candidate, context })).toEqual(
'.foo .\\[\\.foo_\\&\\]\\:tw-text-center'
)
})
it('Merged selectors with mixed combinators uses the first one', () => {
// This isn't explicitly specced behavior but it is how it works today
let selector = '.text-center'
let candidate = 'text-center'
let formats = [
{ format: ':merge(.group):focus > &', respectPrefix: false },
{ format: ':merge(.group):hover &', respectPrefix: false },
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.group:hover:focus > .text-center'
)
})
describe('real examples', () => {
it('example a', () => {
let selector = '.placeholder-red-500::placeholder'
let candidate = 'hover:placeholder-red-500'
let formats = [{ format: '&:hover', respectPrefix: true }]
expect(finalizeSelector(selector, formats, { candidate })).toEqual('.hover\\:text-center:hover')
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.hover\\:placeholder-red-500:hover::placeholder'
)
})
it('should be possible to add a multiple simple variants to a simple selector', () => {
let selector = '.text-center'
let candidate = 'focus:hover:text-center'
it('example b', () => {
let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
let candidate = 'group-hover:hover:space-x-4'
let formats = [
{ format: '&:hover', respectPrefix: true },
{ format: '&:focus', respectPrefix: true },
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.focus\\:hover\\:text-center:hover:focus'
)
})
it('should be possible to add a simple variant to a selector containing escaped parts', () => {
let selector = '.bg-\\[rgba\\(0\\,0\\,0\\)\\]'
let candidate = 'hover:bg-[rgba(0,0,0)]'
let formats = [{ format: '&:hover', respectPrefix: true }]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.hover\\:bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]:hover'
)
})
it('should be possible to add a simple variant to a selector containing escaped parts (escape is slightly different)', () => {
let selector = '.bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]'
let candidate = 'hover:bg-[rgba(0,0,0)]'
let formats = [{ format: '&:hover', respectPrefix: true }]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.hover\\:bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]:hover'
)
})
it('should be possible to add a simple variant to a more complex selector', () => {
let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
let candidate = 'hover:space-x-4'
let formats = [{ format: '&:hover', respectPrefix: true }]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.hover\\:space-x-4:hover > :not([hidden]) ~ :not([hidden])'
)
})
it('should be possible to add multiple simple variants to a more complex selector', () => {
let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
let candidate = 'disabled:focus:hover:space-x-4'
let formats = [
{ format: '&:hover', respectPrefix: true },
{ format: '&:focus', respectPrefix: true },
{ format: '&:disabled', respectPrefix: true },
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.disabled\\:focus\\:hover\\:space-x-4:hover:focus:disabled > :not([hidden]) ~ :not([hidden])'
)
})
it('should be possible to add a single merge variant to a simple selector', () => {
let selector = '.text-center'
let candidate = 'group-hover:text-center'
let formats = [{ format: ':merge(.group):hover &', respectPrefix: true }]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.group:hover .group-hover\\:text-center'
)
})
it('should be possible to add multiple merge variants to a simple selector', () => {
let selector = '.text-center'
let candidate = 'group-focus:group-hover:text-center'
let formats = [
{ format: ':merge(.group):hover &', respectPrefix: true },
{ format: ':merge(.group):focus &', respectPrefix: true },
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.group:focus:hover .group-focus\\:group-hover\\:text-center'
)
})
it('should be possible to add a single merge variant to a more complex selector', () => {
let selector = '.space-x-4 ~ :not([hidden]) ~ :not([hidden])'
let candidate = 'group-hover:space-x-4'
let formats = [{ format: ':merge(.group):hover &', respectPrefix: true }]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.group:hover .group-hover\\:space-x-4 ~ :not([hidden]) ~ :not([hidden])'
)
})
it('should be possible to add multiple merge variants to a more complex selector', () => {
let selector = '.space-x-4 ~ :not([hidden]) ~ :not([hidden])'
let candidate = 'group-focus:group-hover:space-x-4'
let formats = [
{ format: ':merge(.group):hover &', respectPrefix: true },
{ format: ':merge(.group):focus &', respectPrefix: true },
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.group:focus:hover .group-focus\\:group-hover\\:space-x-4 ~ :not([hidden]) ~ :not([hidden])'
)
})
it('should be possible to add multiple unique merge variants to a simple selector', () => {
let selector = '.text-center'
let candidate = 'peer-focus:group-hover:text-center'
let formats = [
{ format: ':merge(.group):hover &', respectPrefix: true },
{ format: ':merge(.peer):focus ~ &' },
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.peer:focus ~ .group:hover .peer-focus\\:group-hover\\:text-center'
)
})
it('should be possible to add multiple unique merge variants to a simple selector', () => {
let selector = '.text-center'
let candidate = 'group-hover:peer-focus:text-center'
let formats = [
{ format: ':merge(.peer):focus ~ &', respectPrefix: true },
{ format: ':merge(.group):hover &', respectPrefix: true },
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.group:hover .peer:focus ~ .group-hover\\:peer-focus\\:text-center'
'.group:hover .group-hover\\:hover\\:space-x-4:hover > :not([hidden]) ~ :not([hidden])'
)
})
it('should be possible to use multiple :merge() calls with different "arguments"', () => {
let selector = '.foo'
let candidate = 'peer-focus:group-focus:peer-hover:group-hover:foo'
it('should work for group-hover and class dark mode combinations', () => {
let selector = '.text-center'
let candidate = 'dark:group-hover:text-center'
let formats = [
{ format: ':merge(.group):hover &', respectPrefix: true },
{ format: ':merge(.peer):hover ~ &', respectPrefix: true },
{ format: ':merge(.group):focus &', respectPrefix: true },
{ format: ':merge(.peer):focus ~ &', respectPrefix: true },
{ format: '.dark &', respectPrefix: true },
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.peer:focus:hover ~ .group:focus:hover .peer-focus\\:group-focus\\:peer-hover\\:group-hover\\:foo'
'.dark .group:hover .dark\\:group-hover\\:text-center'
)
})
it('group hover and prose headings combination', () => {
it('should work for group-hover and class dark mode combinations (reversed)', () => {
let selector = '.text-center'
let candidate = 'group-hover:prose-headings:text-center'
let formats = [
{ format: ':where(&) :is(h1, h2, h3, h4)', respectPrefix: true }, // Prose Headings
{ format: ':merge(.group):hover &', respectPrefix: true }, // Group Hover
]
let candidate = 'group-hover:dark:text-center'
let formats = [{ format: '.dark &' }, { format: ':merge(.group):hover &', respectPrefix: true }]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.group:hover :where(.group-hover\\:prose-headings\\:text-center) :is(h1, h2, h3, h4)'
'.group:hover .dark .group-hover\\:dark\\:text-center'
)
})
it('group hover and prose headings combination flipped', () => {
let selector = '.text-center'
let candidate = 'prose-headings:group-hover:text-center'
let formats = [
{ format: ':merge(.group):hover &', respectPrefix: true }, // Group Hover
{ format: ':where(&) :is(h1, h2, h3, h4)', respectPrefix: true }, // Prose Headings
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
':where(.group:hover .prose-headings\\:group-hover\\:text-center) :is(h1, h2, h3, h4)'
)
})
it('should be possible to handle a complex utility', () => {
let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
let candidate = 'peer-disabled:peer-first-child:group-hover:group-focus:focus:hover:space-x-4'
let formats = [
{ format: '&:hover', respectPrefix: true }, // Hover
{ format: '&:focus', respectPrefix: true }, // Focus
{ format: ':merge(.group):focus &', respectPrefix: true }, // Group focus
{ format: ':merge(.group):hover &', respectPrefix: true }, // Group hover
{ format: ':merge(.peer):first-child ~ &', respectPrefix: true }, // Peer first-child
{ format: ':merge(.peer):disabled ~ &', respectPrefix: true }, // Peer disabled
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.peer:disabled:first-child ~ .group:hover:focus .peer-disabled\\:peer-first-child\\:group-hover\\:group-focus\\:focus\\:hover\\:space-x-4:hover:focus > :not([hidden]) ~ :not([hidden])'
)
})
it('should match base utilities that are prefixed', () => {
let context = { tailwindConfig: { prefix: 'tw-' } }
let selector = '.tw-text-center'
let candidate = 'tw-text-center'
let formats = []
expect(finalizeSelector(selector, formats, { candidate, context })).toEqual('.tw-text-center')
})
it('should prefix classes from variants', () => {
let context = { tailwindConfig: { prefix: 'tw-' } }
let selector = '.tw-text-center'
let candidate = 'foo:tw-text-center'
let formats = [{ format: '.foo &', respectPrefix: true }]
expect(finalizeSelector(selector, formats, { candidate, context })).toEqual(
'.tw-foo .foo\\:tw-text-center'
)
})
it('should not prefix classes from arbitrary variants', () => {
let context = { tailwindConfig: { prefix: 'tw-' } }
let selector = '.tw-text-center'
let candidate = '[.foo_&]:tw-text-center'
let formats = [{ format: '.foo &', respectPrefix: false }]
expect(finalizeSelector(selector, formats, { candidate, context })).toEqual(
'.foo .\\[\\.foo_\\&\\]\\:tw-text-center'
)
})
it('Merged selectors with mixed combinators uses the first one', () => {
// This isn't explicitly specced behavior but it is how it works today
let selector = '.text-center'
let candidate = 'text-center'
let formats = [
{ format: ':merge(.group):focus > &', respectPrefix: false },
{ format: ':merge(.group):hover &', respectPrefix: false },
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.group:hover:focus > .text-center'
)
})
describe('real examples', () => {
it('example a', () => {
let selector = '.placeholder-red-500::placeholder'
let candidate = 'hover:placeholder-red-500'
let formats = [{ format: '&:hover', respectPrefix: true }]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.hover\\:placeholder-red-500:hover::placeholder'
)
})
it('example b', () => {
let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
let candidate = 'group-hover:hover:space-x-4'
let formats = [
{ format: '&:hover', respectPrefix: true },
{ format: ':merge(.group):hover &', respectPrefix: true },
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.group:hover .group-hover\\:hover\\:space-x-4:hover > :not([hidden]) ~ :not([hidden])'
)
})
it('should work for group-hover and class dark mode combinations', () => {
describe('prose-headings', () => {
it('should be possible to use hover:prose-headings:text-center', () => {
let selector = '.text-center'
let candidate = 'dark:group-hover:text-center'
let candidate = 'hover:prose-headings:text-center'
let formats = [
{ format: ':merge(.group):hover &', respectPrefix: true },
{ format: '.dark &', respectPrefix: true },
]
let formats = [{ format: ':where(&) :is(h1, h2, h3, h4)' }, { format: '&:hover' }]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.dark .group:hover .dark\\:group-hover\\:text-center'
':where(.hover\\:prose-headings\\:text-center) :is(h1, h2, h3, h4):hover'
)
})
it('should work for group-hover and class dark mode combinations (reversed)', () => {
it('should be possible to use prose-headings:hover:text-center', () => {
let selector = '.text-center'
let candidate = 'group-hover:dark:text-center'
let candidate = 'prose-headings:hover:text-center'
let formats = [
{ format: '.dark &' },
{ format: ':merge(.group):hover &', respectPrefix: true },
]
let formats = [{ format: '&:hover' }, { format: ':where(&) :is(h1, h2, h3, h4)' }]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
'.group:hover .dark .group-hover\\:dark\\:text-center'
':where(.prose-headings\\:hover\\:text-center:hover) :is(h1, h2, h3, h4)'
)
})
describe('prose-headings', () => {
it('should be possible to use hover:prose-headings:text-center', () => {
let selector = '.text-center'
let candidate = 'hover:prose-headings:text-center'
let formats = [{ format: ':where(&) :is(h1, h2, h3, h4)' }, { format: '&:hover' }]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
':where(.hover\\:prose-headings\\:text-center) :is(h1, h2, h3, h4):hover'
)
})
it('should be possible to use prose-headings:hover:text-center', () => {
let selector = '.text-center'
let candidate = 'prose-headings:hover:text-center'
let formats = [{ format: '&:hover' }, { format: ':where(&) :is(h1, h2, h3, h4)' }]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
':where(.prose-headings\\:hover\\:text-center:hover) :is(h1, h2, h3, h4)'
)
})
})
})
describe('pseudo elements', () => {
it.each`
before | after
${'&::before'} | ${'&::before'}
${'&::before:hover'} | ${'&:hover::before'}
${'&:before:hover'} | ${'&:hover:before'}
${'&::file-selector-button:hover'} | ${'&::file-selector-button:hover'}
${'&:hover::file-selector-button'} | ${'&:hover::file-selector-button'}
${'.parent:hover &'} | ${'.parent:hover &'}
${'.parent::before &'} | ${'.parent &::before'}
${'.parent::before &:hover'} | ${'.parent &:hover::before'}
${':where(&::before) :is(h1, h2, h3, h4)'} | ${':where(&) :is(h1, h2, h3, h4)::before'}
${':where(&::file-selector-button) :is(h1, h2, h3, h4)'} | ${':where(&::file-selector-button) :is(h1, h2, h3, h4)'}
${'#app :is(:where(.dark) &::before)'} | ${'#app :is(:where(.dark) &)::before'}
${'#app :is(:is(:where(.dark) &)::before)'} | ${'#app :is(:is(:where(.dark) &))::before'}
${'#app :is(.foo::file-selector-button)'} | ${'#app :is(.foo)::file-selector-button'}
${'#app :is(.foo::-webkit-progress-bar)'} | ${'#app :is(.foo)::-webkit-progress-bar'}
${'.parent::marker li'} | ${'.parent li::marker'}
${'.parent::selection li'} | ${'.parent li::selection'}
${'.parent::placeholder input'} | ${'.parent input::placeholder'}
${'.parent::backdrop dialog'} | ${'.parent dialog::backdrop'}
`('should translate "$before" into "$after"', ({ before, after }) => {
let result = finalizeSelector('.a', [{ format: before, respectPrefix: true }], {
candidate: 'a',
})
})
describe('pseudo elements', () => {
it.each`
before | after
${'&::before'} | ${'&::before'}
${'&::before:hover'} | ${'&:hover::before'}
${'&:before:hover'} | ${'&:hover:before'}
${'&::file-selector-button:hover'} | ${'&::file-selector-button:hover'}
${'&:hover::file-selector-button'} | ${'&:hover::file-selector-button'}
${'.parent:hover &'} | ${'.parent:hover &'}
${'.parent::before &'} | ${'.parent &::before'}
${'.parent::before &:hover'} | ${'.parent &:hover::before'}
${':where(&::before) :is(h1, h2, h3, h4)'} | ${':where(&) :is(h1, h2, h3, h4)::before'}
${':where(&::file-selector-button) :is(h1, h2, h3, h4)'} | ${':where(&::file-selector-button) :is(h1, h2, h3, h4)'}
${'#app :is(:where(.dark) &::before)'} | ${'#app :is(:where(.dark) &)::before'}
${'#app :is(:is(:where(.dark) &)::before)'} | ${'#app :is(:is(:where(.dark) &))::before'}
${'#app :is(.foo::file-selector-button)'} | ${'#app :is(.foo)::file-selector-button'}
${'#app :is(.foo::-webkit-progress-bar)'} | ${'#app :is(.foo)::-webkit-progress-bar'}
${'.parent::marker li'} | ${'.parent li::marker'}
${'.parent::selection li'} | ${'.parent li::selection'}
${'.parent::placeholder input'} | ${'.parent input::placeholder'}
${'.parent::backdrop dialog'} | ${'.parent dialog::backdrop'}
`('should translate "$before" into "$after"', ({ before, after }) => {
let result = finalizeSelector('.a', [{ format: before, respectPrefix: true }], {
candidate: 'a',
})
expect(result).toEqual(after.replace('&', '.a'))
})
expect(result).toEqual(after.replace('&', '.a'))
})
})
+38 -40
View File
@@ -1,48 +1,46 @@
import { generateRules } from '../src/lib/generateRules'
import resolveConfig from '../src/public/resolve-config'
import { createContext } from '../src/lib/setupContextUtils'
import { crosscheck, css } from './util/run'
import { css } from './util/run'
crosscheck(() => {
it('should not generate rules that are incorrect', () => {
let config = {
plugins: [
({ matchVariant }) => {
matchVariant('@', (value) => `@container (min-width: ${value})`)
},
],
it('should not generate rules that are incorrect', () => {
let config = {
plugins: [
({ matchVariant }) => {
matchVariant('@', (value) => `@container (min-width: ${value})`)
},
],
}
let context = createContext(resolveConfig(config))
let rules = generateRules(
new Set([
// Invalid, missing `-`
'group[:hover]:underline',
// Invalid, `-` should not be there
'@-[200px]:underline',
// Valid
'group-[:hover]:underline',
'@[200px]:underline',
]),
context
)
// Ensure we only have 2 valid rules
expect(rules).toHaveLength(2)
// Ensure we have the correct values
expect(rules[0][1].toString()).toMatchFormattedCss(css`
.group:hover .group-\[\:hover\]\:underline {
text-decoration-line: underline;
}
let context = createContext(resolveConfig(config))
let rules = generateRules(
new Set([
// Invalid, missing `-`
'group[:hover]:underline',
// Invalid, `-` should not be there
'@-[200px]:underline',
// Valid
'group-[:hover]:underline',
'@[200px]:underline',
]),
context
)
// Ensure we only have 2 valid rules
expect(rules).toHaveLength(2)
// Ensure we have the correct values
expect(rules[0][1].toString()).toMatchFormattedCss(css`
.group:hover .group-\[\:hover\]\:underline {
`)
expect(rules[1][1].toString()).toMatchFormattedCss(css`
@container (min-width: 200px) {
.\@\[200px\]\:underline {
text-decoration-line: underline;
}
`)
expect(rules[1][1].toString()).toMatchFormattedCss(css`
@container (min-width: 200px) {
.\@\[200px\]\:underline {
text-decoration-line: underline;
}
}
`)
})
}
`)
})
+193 -196
View File
@@ -1,234 +1,231 @@
import resolveConfig from '../src/public/resolve-config'
import plugin from '../src/public/create-plugin'
import { createContext } from '../src/lib/setupContextUtils'
import { crosscheck } from './util/run'
crosscheck(() => {
it('should generate every possible class, without variants', () => {
let config = {}
it('should generate every possible class, without variants', () => {
let config = {}
let context = createContext(resolveConfig(config))
let classes = context.getClassList()
expect(classes).toBeInstanceOf(Array)
let context = createContext(resolveConfig(config))
let classes = context.getClassList()
expect(classes).toBeInstanceOf(Array)
// Verify we have a `container` for the 'components' section.
expect(classes).toContain('container')
// Verify we have a `container` for the 'components' section.
expect(classes).toContain('container')
// Verify we handle the DEFAULT case correctly
expect(classes).toContain('border')
// Verify we handle the DEFAULT case correctly
expect(classes).toContain('border')
// Verify we handle negative values correctly
expect(classes).toContain('-inset-1/4')
expect(classes).toContain('-m-0')
expect(classes).not.toContain('-uppercase')
expect(classes).not.toContain('-opacity-50')
// Verify we handle negative values correctly
expect(classes).toContain('-inset-1/4')
expect(classes).toContain('-m-0')
expect(classes).not.toContain('-uppercase')
expect(classes).not.toContain('-opacity-50')
config = { theme: { extend: { margin: { DEFAULT: '5px' } } } }
context = createContext(resolveConfig(config))
classes = context.getClassList()
config = { theme: { extend: { margin: { DEFAULT: '5px' } } } }
context = createContext(resolveConfig(config))
classes = context.getClassList()
expect(classes).not.toContain('-m-DEFAULT')
})
expect(classes).not.toContain('-m-DEFAULT')
})
it('should generate every possible class while handling negatives and prefixes', () => {
let config = { prefix: 'tw-' }
let context = createContext(resolveConfig(config))
let classes = context.getClassList()
expect(classes).toBeInstanceOf(Array)
it('should generate every possible class while handling negatives and prefixes', () => {
let config = { prefix: 'tw-' }
let context = createContext(resolveConfig(config))
let classes = context.getClassList()
expect(classes).toBeInstanceOf(Array)
// Verify we have a `container` for the 'components' section.
expect(classes).toContain('tw-container')
// Verify we have a `container` for the 'components' section.
expect(classes).toContain('tw-container')
// Verify we handle the DEFAULT case correctly
expect(classes).toContain('tw-border')
// Verify we handle the DEFAULT case correctly
expect(classes).toContain('tw-border')
// Verify we handle negative values correctly
expect(classes).toContain('-tw-inset-1/4')
expect(classes).toContain('-tw-m-0')
expect(classes).not.toContain('-tw-uppercase')
expect(classes).not.toContain('-tw-opacity-50')
// Verify we handle negative values correctly
expect(classes).toContain('-tw-inset-1/4')
expect(classes).toContain('-tw-m-0')
expect(classes).not.toContain('-tw-uppercase')
expect(classes).not.toContain('-tw-opacity-50')
// These utilities do work but there's no reason to generate
// them alongside the `-{prefix}-{utility}` versions
expect(classes).not.toContain('tw--inset-1/4')
expect(classes).not.toContain('tw--m-0')
// These utilities do work but there's no reason to generate
// them alongside the `-{prefix}-{utility}` versions
expect(classes).not.toContain('tw--inset-1/4')
expect(classes).not.toContain('tw--m-0')
config = {
prefix: 'tw-',
theme: { extend: { margin: { DEFAULT: '5px' } } },
}
context = createContext(resolveConfig(config))
classes = context.getClassList()
config = {
prefix: 'tw-',
theme: { extend: { margin: { DEFAULT: '5px' } } },
}
context = createContext(resolveConfig(config))
classes = context.getClassList()
expect(classes).not.toContain('-tw-m-DEFAULT')
})
expect(classes).not.toContain('-tw-m-DEFAULT')
})
it('should not generate utilities with opacity by default', () => {
let config = {}
let context = createContext(resolveConfig(config))
let classes = context.getClassList()
it('should not generate utilities with opacity by default', () => {
let config = {}
let context = createContext(resolveConfig(config))
let classes = context.getClassList()
expect(classes).not.toContain('bg-red-500/50')
})
expect(classes).not.toContain('bg-red-500/50')
})
it('should not include metadata by default', () => {
let config = {}
let context = createContext(resolveConfig(config))
let classes = context.getClassList()
it('should not include metadata by default', () => {
let config = {}
let context = createContext(resolveConfig(config))
let classes = context.getClassList()
expect(classes.every((cls) => typeof cls === 'string')).toEqual(true)
expect(classes.every((cls) => typeof cls === 'string')).toEqual(true)
expect(classes).toContain('bg-red-500')
expect(classes).toContain('text-2xl')
})
expect(classes).toContain('bg-red-500')
expect(classes).toContain('text-2xl')
})
it('should generate utilities with modifier data when requested', () => {
let config = {}
let context = createContext(resolveConfig(config))
let classes = context.getClassList({ includeMetadata: true })
it('should generate utilities with modifier data when requested', () => {
let config = {}
let context = createContext(resolveConfig(config))
let classes = context.getClassList({ includeMetadata: true })
expect(classes).not.toContain('bg-red-500')
expect(classes).not.toContain('text-2xl')
expect(classes).not.toContain('bg-red-500')
expect(classes).not.toContain('text-2xl')
expect(classes).toContainEqual([
'bg-red-500',
{
modifiers: [
'0',
'5',
'10',
'15',
'20',
'25',
'30',
'35',
'40',
'45',
'50',
'55',
'60',
'65',
'70',
'75',
'80',
'85',
'90',
'95',
'100',
],
},
])
expect(classes).toContainEqual([
'text-2xl',
{
modifiers: [
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'10',
'none',
'tight',
'snug',
'normal',
'relaxed',
'loose',
],
},
])
})
it('should generate plugin-defined utilities with modifier data when requested', () => {
let config = {
plugins: [
plugin(function ({ matchUtilities }) {
matchUtilities(
{
foo: (value) => {
return { margin: value }
},
},
{
values: { xl: '32px' },
modifiers: { bar: 'something' },
}
)
matchUtilities(
{
'foo-negative': (value) => {
return { margin: value }
},
},
{
values: { xl: '32px' },
modifiers: { bar: 'something' },
supportsNegativeValues: true,
}
)
}),
expect(classes).toContainEqual([
'bg-red-500',
{
modifiers: [
'0',
'5',
'10',
'15',
'20',
'25',
'30',
'35',
'40',
'45',
'50',
'55',
'60',
'65',
'70',
'75',
'80',
'85',
'90',
'95',
'100',
],
}
let context = createContext(resolveConfig(config))
let classes = context.getClassList({ includeMetadata: true })
expect(classes).toContainEqual(['foo-xl', { modifiers: ['bar'] }])
expect(classes).toContainEqual(['foo-negative-xl', { modifiers: ['bar'] }])
expect(classes).toContainEqual(['-foo-negative-xl', { modifiers: ['bar'] }])
expect(classes).not.toContain('foo-xl')
expect(classes).not.toContain('-foo-xl')
expect(classes).not.toContainEqual(['-foo-xl', { modifiers: ['bar'] }])
})
it('should not generate utilities with opacity even if safe-listed', () => {
let config = {
safelist: [
{
pattern: /^bg-red-(400|500)(\/(40|50))?$/,
},
},
])
expect(classes).toContainEqual([
'text-2xl',
{
modifiers: [
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'10',
'none',
'tight',
'snug',
'normal',
'relaxed',
'loose',
],
}
},
])
})
let context = createContext(resolveConfig(config))
let classes = context.getClassList()
expect(classes).not.toContain('bg-red-500/50')
})
it('should not generate utilities that are set to undefined or null to so that they are removed', () => {
let config = {
theme: {
extend: {
colors: {
red: null,
green: undefined,
blue: {
100: null,
200: undefined,
it('should generate plugin-defined utilities with modifier data when requested', () => {
let config = {
plugins: [
plugin(function ({ matchUtilities }) {
matchUtilities(
{
foo: (value) => {
return { margin: value }
},
},
{
values: { xl: '32px' },
modifiers: { bar: 'something' },
}
)
matchUtilities(
{
'foo-negative': (value) => {
return { margin: value }
},
},
{
values: { xl: '32px' },
modifiers: { bar: 'something' },
supportsNegativeValues: true,
}
)
}),
],
}
let context = createContext(resolveConfig(config))
let classes = context.getClassList({ includeMetadata: true })
expect(classes).toContainEqual(['foo-xl', { modifiers: ['bar'] }])
expect(classes).toContainEqual(['foo-negative-xl', { modifiers: ['bar'] }])
expect(classes).toContainEqual(['-foo-negative-xl', { modifiers: ['bar'] }])
expect(classes).not.toContain('foo-xl')
expect(classes).not.toContain('-foo-xl')
expect(classes).not.toContainEqual(['-foo-xl', { modifiers: ['bar'] }])
})
it('should not generate utilities with opacity even if safe-listed', () => {
let config = {
safelist: [
{
pattern: /^bg-red-(400|500)(\/(40|50))?$/,
},
],
}
let context = createContext(resolveConfig(config))
let classes = context.getClassList()
expect(classes).not.toContain('bg-red-500/50')
})
it('should not generate utilities that are set to undefined or null to so that they are removed', () => {
let config = {
theme: {
extend: {
colors: {
red: null,
green: undefined,
blue: {
100: null,
200: undefined,
},
},
},
safelist: [
{
pattern: /^bg-(red|green|blue)-.*$/,
},
],
}
},
safelist: [
{
pattern: /^bg-(red|green|blue)-.*$/,
},
],
}
let context = createContext(resolveConfig(config))
let classes = context.getClassList()
let context = createContext(resolveConfig(config))
let classes = context.getClassList()
expect(classes).not.toContain('bg-red-100') // Red is `null`
expect(classes).not.toContain('bg-red-100') // Red is `null`
expect(classes).not.toContain('bg-green-100') // Green is `undefined`
expect(classes).not.toContain('bg-green-100') // Green is `undefined`
expect(classes).not.toContain('bg-blue-100') // Blue.100 is `null`
expect(classes).not.toContain('bg-blue-200') // Blue.200 is `undefined`
expect(classes).not.toContain('bg-blue-100') // Blue.100 is `null`
expect(classes).not.toContain('bg-blue-200') // Blue.200 is `undefined`
expect(classes).toContain('bg-blue-50')
expect(classes).toContain('bg-blue-300')
})
expect(classes).toContain('bg-blue-50')
expect(classes).toContain('bg-blue-300')
})
+96 -99
View File
@@ -1,7 +1,6 @@
import resolveConfig from '../src/public/resolve-config'
import { createContext } from '../src/lib/setupContextUtils'
import bigSign from '../src/util/bigSign'
import { crosscheck } from './util/run'
/**
* This is a function that the prettier-plugin-tailwindcss would use. It would
@@ -25,123 +24,121 @@ function defaultSort(arrayOfTuples) {
.join(' ')
}
crosscheck(() => {
it('should return a list of tuples with the sort order', () => {
let input = 'font-bold underline hover:font-medium unknown'
let config = {}
let context = createContext(resolveConfig(config))
expect(context.getClassOrder(input.split(' '))).toEqual([
['font-bold', expect.any(BigInt)],
['underline', expect.any(BigInt)],
['hover:font-medium', expect.any(BigInt)],
it('should return a list of tuples with the sort order', () => {
let input = 'font-bold underline hover:font-medium unknown'
let config = {}
let context = createContext(resolveConfig(config))
expect(context.getClassOrder(input.split(' '))).toEqual([
['font-bold', expect.any(BigInt)],
['underline', expect.any(BigInt)],
['hover:font-medium', expect.any(BigInt)],
// Unknown values receive `null`
['unknown', null],
])
})
// Unknown values receive `null`
['unknown', null],
])
})
it.each([
// Utitlies
['px-3 p-1 py-3', 'p-1 px-3 py-3'],
it.each([
// Utitlies
['px-3 p-1 py-3', 'p-1 px-3 py-3'],
// Utitlies and components
['px-4 container', 'container px-4'],
// Utitlies and components
['px-4 container', 'container px-4'],
// Utilities with variants
['px-3 focus:hover:p-3 hover:p-1 py-3', 'px-3 py-3 hover:p-1 focus:hover:p-3'],
// Utilities with variants
['px-3 focus:hover:p-3 hover:p-1 py-3', 'px-3 py-3 hover:p-1 focus:hover:p-3'],
// Utitlies with important
['px-3 !py-4', '!py-4 px-3'],
['!py-4 px-3', '!py-4 px-3'],
// Utitlies with important
['px-3 !py-4', '!py-4 px-3'],
['!py-4 px-3', '!py-4 px-3'],
// Components with variants
['hover:container container', 'container hover:container'],
// Components with variants
['hover:container container', 'container hover:container'],
// Components and utilities with variants
[
'focus:hover:container hover:underline hover:container p-1',
'p-1 hover:container focus:hover:container hover:underline',
],
// Components and utilities with variants
[
'focus:hover:container hover:underline hover:container p-1',
'p-1 hover:container focus:hover:container hover:underline',
],
// Leave user css order alone, and move to the front
['b p-1 a', 'b a p-1'],
['hover:b focus:p-1 a', 'hover:b a focus:p-1'],
// Leave user css order alone, and move to the front
['b p-1 a', 'b a p-1'],
['hover:b focus:p-1 a', 'hover:b a focus:p-1'],
// Add special treatment for `group` and `peer`
['a peer container underline', 'a peer container underline'],
])('should sort "%s" based on the order we generate them in to "%s"', (input, output) => {
let config = {}
// Add special treatment for `group` and `peer`
['a peer container underline', 'a peer container underline'],
])('should sort "%s" based on the order we generate them in to "%s"', (input, output) => {
let config = {}
let context = createContext(resolveConfig(config))
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
})
it.each([
// Utitlies
['tw-px-3 tw-p-1 tw-py-3', 'tw-p-1 tw-px-3 tw-py-3'],
// Utitlies and components
['tw-px-4 tw-container', 'tw-container tw-px-4'],
// Utilities with variants
[
'tw-px-3 focus:hover:tw-p-3 hover:tw-p-1 tw-py-3',
'tw-px-3 tw-py-3 hover:tw-p-1 focus:hover:tw-p-3',
],
// Utitlies with important
['tw-px-3 !tw-py-4', '!tw-py-4 tw-px-3'],
['!tw-py-4 tw-px-3', '!tw-py-4 tw-px-3'],
// Components with variants
['hover:tw-container tw-container', 'tw-container hover:tw-container'],
// Components and utilities with variants
[
'focus:hover:tw-container hover:tw-underline hover:tw-container tw-p-1',
'tw-p-1 hover:tw-container focus:hover:tw-container hover:tw-underline',
],
// Leave user css order alone, and move to the front
['b tw-p-1 a', 'b a tw-p-1'],
['hover:b focus:tw-p-1 a', 'hover:b a focus:tw-p-1'],
// Add special treatment for `group` and `peer`
['a tw-peer tw-container tw-underline', 'a tw-peer tw-container tw-underline'],
])(
'should sort "%s" with prefixex based on the order we generate them in to "%s"',
(input, output) => {
let config = { prefix: 'tw-' }
let context = createContext(resolveConfig(config))
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
})
}
)
it.each([
// Utitlies
['tw-px-3 tw-p-1 tw-py-3', 'tw-p-1 tw-px-3 tw-py-3'],
// Utitlies and components
['tw-px-4 tw-container', 'tw-container tw-px-4'],
// Utilities with variants
it('sorts classes deterministically across multiple class lists', () => {
let classes = [
[
'tw-px-3 focus:hover:tw-p-3 hover:tw-p-1 tw-py-3',
'tw-px-3 tw-py-3 hover:tw-p-1 focus:hover:tw-p-3',
'a-class px-3 p-1 b-class py-3 bg-red-500 bg-blue-500',
'a-class b-class bg-blue-500 bg-red-500 p-1 px-3 py-3',
],
// Utitlies with important
['tw-px-3 !tw-py-4', '!tw-py-4 tw-px-3'],
['!tw-py-4 tw-px-3', '!tw-py-4 tw-px-3'],
// Components with variants
['hover:tw-container tw-container', 'tw-container hover:tw-container'],
// Components and utilities with variants
[
'focus:hover:tw-container hover:tw-underline hover:tw-container tw-p-1',
'tw-p-1 hover:tw-container focus:hover:tw-container hover:tw-underline',
'px-3 b-class p-1 py-3 bg-blue-500 a-class bg-red-500',
'b-class a-class bg-blue-500 bg-red-500 p-1 px-3 py-3',
],
]
// Leave user css order alone, and move to the front
['b tw-p-1 a', 'b a tw-p-1'],
['hover:b focus:tw-p-1 a', 'hover:b a focus:tw-p-1'],
let config = {}
// Add special treatment for `group` and `peer`
['a tw-peer tw-container tw-underline', 'a tw-peer tw-container tw-underline'],
])(
'should sort "%s" with prefixex based on the order we generate them in to "%s"',
(input, output) => {
let config = { prefix: 'tw-' }
let context = createContext(resolveConfig(config))
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
}
)
// Same context, different class lists
let context = createContext(resolveConfig(config))
for (const [input, output] of classes) {
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
}
it('sorts classes deterministically across multiple class lists', () => {
let classes = [
[
'a-class px-3 p-1 b-class py-3 bg-red-500 bg-blue-500',
'a-class b-class bg-blue-500 bg-red-500 p-1 px-3 py-3',
],
[
'px-3 b-class p-1 py-3 bg-blue-500 a-class bg-red-500',
'b-class a-class bg-blue-500 bg-red-500 p-1 px-3 py-3',
],
]
let config = {}
// Same context, different class lists
let context = createContext(resolveConfig(config))
for (const [input, output] of classes) {
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
}
// Different context, different class lists
for (const [input, output] of classes) {
context = createContext(resolveConfig(config))
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
}
})
// Different context, different class lists
for (const [input, output] of classes) {
context = createContext(resolveConfig(config))
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
}
})
it('sorts based on first occurence of a candidate / rule', () => {
+165 -168
View File
@@ -2,207 +2,204 @@ import postcss from 'postcss'
import selectorParser from 'postcss-selector-parser'
import resolveConfig from '../src/public/resolve-config'
import { createContext } from '../src/lib/setupContextUtils'
import { crosscheck } from './util/run'
crosscheck(() => {
it('should return a list of variants with meta information about the variant', () => {
let config = {}
let context = createContext(resolveConfig(config))
it('should return a list of variants with meta information about the variant', () => {
let config = {}
let context = createContext(resolveConfig(config))
let variants = context.getVariants()
let variants = context.getVariants()
expect(variants).toContainEqual({
name: 'hover',
isArbitrary: false,
hasDash: true,
values: [],
selectors: expect.any(Function),
})
expect(variants).toContainEqual({
name: 'group',
isArbitrary: true,
hasDash: true,
values: expect.any(Array),
selectors: expect.any(Function),
})
// `group-hover` now belongs to the `group` variant. The information exposed for the `group`
// variant is all you need.
expect(variants.find((v) => v.name === 'group-hover')).toBeUndefined()
expect(variants).toContainEqual({
name: 'hover',
isArbitrary: false,
hasDash: true,
values: [],
selectors: expect.any(Function),
})
it('should provide selectors for simple variants', () => {
let config = {}
let context = createContext(resolveConfig(config))
let variants = context.getVariants()
let variant = variants.find((v) => v.name === 'hover')
expect(variant.selectors()).toEqual(['&:hover'])
expect(variants).toContainEqual({
name: 'group',
isArbitrary: true,
hasDash: true,
values: expect.any(Array),
selectors: expect.any(Function),
})
it('should provide selectors for parallel variants', () => {
let config = {}
let context = createContext(resolveConfig(config))
// `group-hover` now belongs to the `group` variant. The information exposed for the `group`
// variant is all you need.
expect(variants.find((v) => v.name === 'group-hover')).toBeUndefined()
})
let variants = context.getVariants()
it('should provide selectors for simple variants', () => {
let config = {}
let context = createContext(resolveConfig(config))
let variant = variants.find((v) => v.name === 'marker')
expect(variant.selectors()).toEqual(['& *::marker', '&::marker'])
})
let variants = context.getVariants()
it('should provide selectors for complex matchVariant variants like `group`', () => {
let config = {}
let context = createContext(resolveConfig(config))
let variant = variants.find((v) => v.name === 'hover')
expect(variant.selectors()).toEqual(['&:hover'])
})
let variants = context.getVariants()
it('should provide selectors for parallel variants', () => {
let config = {}
let context = createContext(resolveConfig(config))
let variant = variants.find((v) => v.name === 'group')
expect(variant.selectors()).toEqual(['.group &'])
expect(variant.selectors({})).toEqual(['.group &'])
expect(variant.selectors({ value: 'hover' })).toEqual(['.group:hover &'])
expect(variant.selectors({ value: '.foo_&' })).toEqual(['.foo .group &'])
expect(variant.selectors({ modifier: 'foo', value: 'hover' })).toEqual(['.group\\/foo:hover &'])
expect(variant.selectors({ modifier: 'foo', value: '.foo_&' })).toEqual(['.foo .group\\/foo &'])
})
let variants = context.getVariants()
it('should provide selectors for complex matchVariant variants like `group` (when using a prefix)', () => {
let config = { prefix: 'tw-' }
let context = createContext(resolveConfig(config))
let variant = variants.find((v) => v.name === 'marker')
expect(variant.selectors()).toEqual(['& *::marker', '&::marker'])
})
let variants = context.getVariants()
it('should provide selectors for complex matchVariant variants like `group`', () => {
let config = {}
let context = createContext(resolveConfig(config))
let variant = variants.find((v) => v.name === 'group')
expect(variant.selectors()).toEqual(['.tw-group &'])
expect(variant.selectors({})).toEqual(['.tw-group &'])
expect(variant.selectors({ value: 'hover' })).toEqual(['.tw-group:hover &'])
expect(variant.selectors({ value: '.foo_&' })).toEqual(['.foo .tw-group &'])
expect(variant.selectors({ modifier: 'foo', value: 'hover' })).toEqual([
'.tw-group\\/foo:hover &',
])
expect(variant.selectors({ modifier: 'foo', value: '.foo_&' })).toEqual([
'.foo .tw-group\\/foo &',
])
})
let variants = context.getVariants()
it('should provide selectors for variants with atrules', () => {
let config = {}
let context = createContext(resolveConfig(config))
let variant = variants.find((v) => v.name === 'group')
expect(variant.selectors()).toEqual(['.group &'])
expect(variant.selectors({})).toEqual(['.group &'])
expect(variant.selectors({ value: 'hover' })).toEqual(['.group:hover &'])
expect(variant.selectors({ value: '.foo_&' })).toEqual(['.foo .group &'])
expect(variant.selectors({ modifier: 'foo', value: 'hover' })).toEqual(['.group\\/foo:hover &'])
expect(variant.selectors({ modifier: 'foo', value: '.foo_&' })).toEqual(['.foo .group\\/foo &'])
})
let variants = context.getVariants()
it('should provide selectors for complex matchVariant variants like `group` (when using a prefix)', () => {
let config = { prefix: 'tw-' }
let context = createContext(resolveConfig(config))
let variant = variants.find((v) => v.name === 'supports')
expect(variant.selectors({ value: 'display:grid' })).toEqual(['@supports (display:grid)'])
expect(variant.selectors({ value: 'aspect-ratio' })).toEqual([
'@supports (aspect-ratio: var(--tw))',
])
})
let variants = context.getVariants()
it('should provide selectors for custom plugins that do a combination of parallel variants with modifiers with arbitrary values and with atrules', () => {
let config = {
plugins: [
function ({ matchVariant }) {
matchVariant('foo', (value, { modifier }) => {
return [
`
let variant = variants.find((v) => v.name === 'group')
expect(variant.selectors()).toEqual(['.tw-group &'])
expect(variant.selectors({})).toEqual(['.tw-group &'])
expect(variant.selectors({ value: 'hover' })).toEqual(['.tw-group:hover &'])
expect(variant.selectors({ value: '.foo_&' })).toEqual(['.foo .tw-group &'])
expect(variant.selectors({ modifier: 'foo', value: 'hover' })).toEqual([
'.tw-group\\/foo:hover &',
])
expect(variant.selectors({ modifier: 'foo', value: '.foo_&' })).toEqual([
'.foo .tw-group\\/foo &',
])
})
it('should provide selectors for variants with atrules', () => {
let config = {}
let context = createContext(resolveConfig(config))
let variants = context.getVariants()
let variant = variants.find((v) => v.name === 'supports')
expect(variant.selectors({ value: 'display:grid' })).toEqual(['@supports (display:grid)'])
expect(variant.selectors({ value: 'aspect-ratio' })).toEqual([
'@supports (aspect-ratio: var(--tw))',
])
})
it('should provide selectors for custom plugins that do a combination of parallel variants with modifiers with arbitrary values and with atrules', () => {
let config = {
plugins: [
function ({ matchVariant }) {
matchVariant('foo', (value, { modifier }) => {
return [
`
@supports (foo: ${modifier}) {
@media (width <= 400px) {
&:hover
}
}
`,
`.${modifier}\\/${value} &:focus`,
]
})
},
],
}
let context = createContext(resolveConfig(config))
`.${modifier}\\/${value} &:focus`,
]
})
},
],
}
let context = createContext(resolveConfig(config))
let variants = context.getVariants()
let variants = context.getVariants()
let variant = variants.find((v) => v.name === 'foo')
expect(variant.selectors({ modifier: 'bar', value: 'baz' })).toEqual([
'@supports (foo: bar) { @media (width <= 400px) { &:hover } }',
'.bar\\/baz &:focus',
])
})
let variant = variants.find((v) => v.name === 'foo')
expect(variant.selectors({ modifier: 'bar', value: 'baz' })).toEqual([
'@supports (foo: bar) { @media (width <= 400px) { &:hover } }',
'.bar\\/baz &:focus',
])
})
it('should work for plugins that still use the modifySelectors API', () => {
let config = {
plugins: [
function ({ addVariant }) {
addVariant('foo', ({ modifySelectors, container }) => {
// Manually mutating the selector
modifySelectors(({ selector }) => {
return selectorParser((selectors) => {
selectors.walkClasses((classNode) => {
classNode.value = `foo:${classNode.value}`
classNode.parent.insertBefore(classNode, selectorParser().astSync(`.foo `))
})
}).processSync(selector)
})
it('should work for plugins that still use the modifySelectors API', () => {
let config = {
plugins: [
function ({ addVariant }) {
addVariant('foo', ({ modifySelectors, container }) => {
// Manually mutating the selector
modifySelectors(({ selector }) => {
return selectorParser((selectors) => {
selectors.walkClasses((classNode) => {
classNode.value = `foo:${classNode.value}`
classNode.parent.insertBefore(classNode, selectorParser().astSync(`.foo `))
})
}).processSync(selector)
})
// Manually wrap in supports query
let wrapper = postcss.atRule({ name: 'supports', params: 'display: grid' })
let nodes = container.nodes
container.removeAll()
wrapper.append(nodes)
container.append(wrapper)
})
},
],
}
let context = createContext(resolveConfig(config))
// Manually wrap in supports query
let wrapper = postcss.atRule({ name: 'supports', params: 'display: grid' })
let nodes = container.nodes
container.removeAll()
wrapper.append(nodes)
container.append(wrapper)
})
},
],
}
let context = createContext(resolveConfig(config))
let variants = context.getVariants()
let variants = context.getVariants()
let variant = variants.find((v) => v.name === 'foo')
expect(variant.selectors({})).toEqual(['@supports (display: grid) { .foo .foo\\:& }'])
})
let variant = variants.find((v) => v.name === 'foo')
expect(variant.selectors({})).toEqual(['@supports (display: grid) { .foo .foo\\:& }'])
})
it('should special case the `@`', () => {
let config = {
plugins: [
({ matchVariant }) => {
matchVariant(
'@',
(value, { modifier }) => `@container ${modifier ?? ''} (min-width: ${value})`,
{
modifiers: 'any',
values: {
xs: '20rem',
sm: '24rem',
md: '28rem',
lg: '32rem',
xl: '36rem',
'2xl': '42rem',
'3xl': '48rem',
'4xl': '56rem',
'5xl': '64rem',
'6xl': '72rem',
'7xl': '80rem',
},
}
)
},
],
}
let context = createContext(resolveConfig(config))
it('should special case the `@`', () => {
let config = {
plugins: [
({ matchVariant }) => {
matchVariant(
'@',
(value, { modifier }) => `@container ${modifier ?? ''} (min-width: ${value})`,
{
modifiers: 'any',
values: {
xs: '20rem',
sm: '24rem',
md: '28rem',
lg: '32rem',
xl: '36rem',
'2xl': '42rem',
'3xl': '48rem',
'4xl': '56rem',
'5xl': '64rem',
'6xl': '72rem',
'7xl': '80rem',
},
}
)
},
],
}
let context = createContext(resolveConfig(config))
let variants = context.getVariants()
let variants = context.getVariants()
let variant = variants.find((v) => v.name === '@')
expect(variant).toEqual({
name: '@',
isArbitrary: true,
hasDash: false,
values: expect.any(Array),
selectors: expect.any(Function),
})
expect(variant.selectors({ value: 'xs', modifier: 'foo' })).toEqual([
'@container foo (min-width: 20rem)',
])
let variant = variants.find((v) => v.name === '@')
expect(variant).toEqual({
name: '@',
isArbitrary: true,
hasDash: false,
values: expect.any(Array),
selectors: expect.any(Function),
})
expect(variant.selectors({ value: 'xs', modifier: 'foo' })).toEqual([
'@container foo (min-width: 20rem)',
])
})
+69 -116
View File
@@ -1,129 +1,82 @@
import { crosscheck, run, html, css, defaults } from './util/run'
import { run, html, css, defaults } from './util/run'
crosscheck(({ stable, oxide }) => {
test('using @import instead of @tailwind', () => {
let config = {
content: [
{
raw: html`
<h1>Hello world!</h1>
<div class="container"></div>
<div class="mt-6"></div>
<div class="bg-black"></div>
<div class="md:hover:text-center"></div>
`,
},
],
corePlugins: { preflight: false },
plugins: [
function ({ addBase }) {
addBase({
h1: {
fontSize: '32px',
},
})
},
],
}
test('using @import instead of @tailwind', () => {
let config = {
content: [
{
raw: html`
<h1>Hello world!</h1>
<div class="container"></div>
<div class="mt-6"></div>
<div class="bg-black"></div>
<div class="md:hover:text-center"></div>
`,
},
],
corePlugins: { preflight: false },
plugins: [
function ({ addBase }) {
addBase({
h1: {
fontSize: '32px',
},
})
},
],
}
let input = css`
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
`
let input = css`
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
`
return run(input, config).then((result) => {
stable.expect(result.css).toMatchFormattedCss(css`
h1 {
font-size: 32px;
}
${defaults}
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
h1 {
font-size: 32px;
}
${defaults}
.container {
width: 100%;
}
@media (min-width: 640px) {
.container {
width: 100%;
max-width: 640px;
}
@media (min-width: 640px) {
.container {
max-width: 640px;
}
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
}
@media (min-width: 1536px) {
.container {
max-width: 1536px;
}
}
.mt-6 {
margin-top: 1.5rem;
}
.bg-black {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
@media (min-width: 768px) {
.md\:hover\:text-center:hover {
text-align: center;
}
}
`)
oxide.expect(result.css).toMatchFormattedCss(css`
h1 {
font-size: 32px;
}
${defaults}
}
@media (min-width: 768px) {
.container {
width: 100%;
max-width: 768px;
}
@media (min-width: 640px) {
.container {
max-width: 640px;
}
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
}
@media (min-width: 1536px) {
.container {
max-width: 1536px;
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
}
.mt-6 {
margin-top: 1.5rem;
}
.bg-black {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
@media (min-width: 768px) {
.md\:hover\:text-center:hover {
text-align: center;
}
@media (min-width: 1536px) {
.container {
max-width: 1536px;
}
}
.mt-6 {
margin-top: 1.5rem;
}
.bg-black {
background-color: #000;
}
@media (min-width: 768px) {
.md\:hover\:text-center:hover {
text-align: center;
}
}
`)
})
}
`)
})
})
+202 -204
View File
@@ -2,230 +2,228 @@ import fs from 'fs'
import path from 'path'
import * as sharedState from '../src/lib/sharedState'
import { crosscheck, run, html, css, defaults } from './util/run'
import { run, html, css, defaults } from './util/run'
crosscheck(() => {
test('important boolean', () => {
let config = {
important: true,
darkMode: 'selector',
content: [
{
raw: html`
<div class="container"></div>
<div class="btn"></div>
<div class="animate-spin"></div>
<div class="custom-util"></div>
<div class="custom-component"></div>
<div class="custom-important-component"></div>
<div class="font-bold"></div>
<div class="md:hover:text-right"></div>
<div class="motion-safe:hover:text-center"></div>
<div class="dark:focus:text-left"></div>
<div class="group-hover:focus-within:text-left"></div>
<div class="rtl:active:text-center"></div>
`,
},
],
corePlugins: { preflight: false },
plugins: [
function ({ addComponents, addUtilities }) {
addComponents(
{
'.btn': {
button: 'yes',
},
test('important boolean', () => {
let config = {
important: true,
darkMode: 'selector',
content: [
{
raw: html`
<div class="container"></div>
<div class="btn"></div>
<div class="animate-spin"></div>
<div class="custom-util"></div>
<div class="custom-component"></div>
<div class="custom-important-component"></div>
<div class="font-bold"></div>
<div class="md:hover:text-right"></div>
<div class="motion-safe:hover:text-center"></div>
<div class="dark:focus:text-left"></div>
<div class="group-hover:focus-within:text-left"></div>
<div class="rtl:active:text-center"></div>
`,
},
],
corePlugins: { preflight: false },
plugins: [
function ({ addComponents, addUtilities }) {
addComponents(
{
'.btn': {
button: 'yes',
},
{ respectImportant: true }
)
addComponents(
{
'@font-face': {
'font-family': 'Inter',
},
'@page': {
margin: '1cm',
},
},
{ respectImportant: true }
)
addComponents(
{
'@font-face': {
'font-family': 'Inter',
},
{ respectImportant: true }
)
addUtilities(
{
'.custom-util': {
button: 'no',
},
'@page': {
margin: '1cm',
},
{ respectImportant: false }
)
},
],
},
{ respectImportant: true }
)
addUtilities(
{
'.custom-util': {
button: 'no',
},
},
{ respectImportant: false }
)
},
],
}
let input = css`
@tailwind base;
@tailwind components;
@layer components {
.custom-component {
@apply font-bold;
}
.custom-important-component {
@apply text-center !important;
}
}
@tailwind utilities;
`
let input = css`
@tailwind base;
@tailwind components;
@layer components {
.custom-component {
@apply font-bold;
}
.custom-important-component {
@apply text-center !important;
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.container {
width: 100%;
}
@media (min-width: 640px) {
.container {
max-width: 640px;
}
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
}
@media (min-width: 1536px) {
.container {
max-width: 1536px;
}
}
.btn {
button: yes !important;
}
@font-face {
font-family: Inter;
}
@page {
margin: 1cm;
}
.custom-component {
font-weight: 700;
}
.custom-important-component {
text-align: center !important;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.animate-spin {
animation: 1s linear infinite spin !important;
}
.font-bold {
font-weight: 700 !important;
}
.custom-util {
button: no;
}
.group:hover .group-hover\:focus-within\:text-left:focus-within {
text-align: left !important;
}
@media (prefers-reduced-motion: no-preference) {
.motion-safe\:hover\:text-center:hover {
text-align: center !important;
}
}
@media (min-width: 768px) {
.md\:hover\:text-right:hover {
text-align: right !important;
}
}
.rtl\:active\:text-center:active:where([dir='rtl'], [dir='rtl'] *) {
text-align: center !important;
}
.dark\:focus\:text-left:focus:where(.dark, .dark *) {
text-align: left !important;
}
`)
})
})
// This is in a describe block so we can use `afterEach` :)
describe('duplicate elision', () => {
let filePath = path.resolve(__dirname, './important-boolean-duplicates.test.html')
afterEach(async () => await fs.promises.unlink(filePath))
test('important rules are not duplicated when rebuilding', async () => {
let config = {
important: true,
content: [filePath],
}
await fs.promises.writeFile(
config.content[0],
html`
<div class="ml-2"></div>
<div class="ml-4"></div>
`
)
let input = css`
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.container {
width: 100%;
}
@media (min-width: 640px) {
.container {
max-width: 640px;
}
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
}
@media (min-width: 1536px) {
.container {
max-width: 1536px;
}
}
.btn {
button: yes !important;
}
@font-face {
font-family: Inter;
}
@page {
margin: 1cm;
}
.custom-component {
font-weight: 700;
}
.custom-important-component {
text-align: center !important;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.animate-spin {
animation: 1s linear infinite spin !important;
}
.font-bold {
font-weight: 700 !important;
}
.custom-util {
button: no;
}
.group:hover .group-hover\:focus-within\:text-left:focus-within {
text-align: left !important;
}
@media (prefers-reduced-motion: no-preference) {
.motion-safe\:hover\:text-center:hover {
text-align: center !important;
}
}
@media (min-width: 768px) {
.md\:hover\:text-right:hover {
text-align: right !important;
}
}
.rtl\:active\:text-center:active:where([dir='rtl'], [dir='rtl'] *) {
text-align: center !important;
}
.dark\:focus\:text-left:focus:where(.dark, .dark *) {
text-align: left !important;
}
`)
})
})
let result = await run(input, config)
let allContexts = Array.from(sharedState.contextMap.values())
// This is in a describe block so we can use `afterEach` :)
describe('duplicate elision', () => {
let filePath = path.resolve(__dirname, './important-boolean-duplicates.test.html')
let context = allContexts[allContexts.length - 1]
afterEach(async () => await fs.promises.unlink(filePath))
let ruleCacheSize1 = context.ruleCache.size
test('important rules are not duplicated when rebuilding', async () => {
let config = {
important: true,
content: [filePath],
expect(result.css).toMatchFormattedCss(css`
.ml-2 {
margin-left: 0.5rem !important;
}
.ml-4 {
margin-left: 1rem !important;
}
`)
await fs.promises.writeFile(
config.content[0],
html`
<div class="ml-2"></div>
<div class="ml-4"></div>
`
)
let input = css`
@tailwind utilities;
await fs.promises.writeFile(
config.content[0],
html`
<div class="ml-2"></div>
<div class="ml-6"></div>
`
)
let result = await run(input, config)
let allContexts = Array.from(sharedState.contextMap.values())
result = await run(input, config)
let context = allContexts[allContexts.length - 1]
let ruleCacheSize2 = context.ruleCache.size
let ruleCacheSize1 = context.ruleCache.size
expect(result.css).toMatchFormattedCss(css`
.ml-2 {
margin-left: 0.5rem !important;
}
.ml-4 {
margin-left: 1rem !important;
}
.ml-6 {
margin-left: 1.5rem !important;
}
`)
expect(result.css).toMatchFormattedCss(css`
.ml-2 {
margin-left: 0.5rem !important;
}
.ml-4 {
margin-left: 1rem !important;
}
`)
await fs.promises.writeFile(
config.content[0],
html`
<div class="ml-2"></div>
<div class="ml-6"></div>
`
)
result = await run(input, config)
let ruleCacheSize2 = context.ruleCache.size
expect(result.css).toMatchFormattedCss(css`
.ml-2 {
margin-left: 0.5rem !important;
}
.ml-4 {
margin-left: 1rem !important;
}
.ml-6 {
margin-left: 1.5rem !important;
}
`)
// The rule cache was effectively doubling in size previously
// because the rule cache was never de-duped
// This ensures this behavior doesn't return
expect(ruleCacheSize2 - ruleCacheSize1).toBeLessThan(10)
})
// The rule cache was effectively doubling in size previously
// because the rule cache was never de-duped
// This ensures this behavior doesn't return
expect(ruleCacheSize2 - ruleCacheSize1).toBeLessThan(10)
})
})
+64 -66
View File
@@ -1,79 +1,77 @@
import { crosscheck, run, html, css, defaults } from './util/run'
import { run, html, css, defaults } from './util/run'
crosscheck(() => {
test('important modifier with prefix', () => {
let config = {
important: false,
prefix: 'tw-',
darkMode: 'selector',
content: [
{
raw: html`<!-- The string "!*" can cause problems if we don't handle it, let's include it -->
<div class="!*"></div>
<div class="!tw-container"></div>
<div class="!tw-font-bold"></div>
<div class="hover:!tw-text-center"></div>
<div class="lg:!tw-opacity-50"></div>
<div class="xl:focus:disabled:!tw-float-right"></div> `,
},
],
corePlugins: { preflight: false },
}
test('important modifier with prefix', () => {
let config = {
important: false,
prefix: 'tw-',
darkMode: 'selector',
content: [
{
raw: html`<!-- The string "!*" can cause problems if we don't handle it, let's include it -->
<div class="!*"></div>
<div class="!tw-container"></div>
<div class="!tw-font-bold"></div>
<div class="hover:!tw-text-center"></div>
<div class="lg:!tw-opacity-50"></div>
<div class="xl:focus:disabled:!tw-float-right"></div> `,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.\!tw-container {
width: 100% !important;
}
@media (min-width: 640px) {
.\!tw-container {
width: 100% !important;
max-width: 640px !important;
}
@media (min-width: 640px) {
.\!tw-container {
max-width: 640px !important;
}
}
@media (min-width: 768px) {
.\!tw-container {
max-width: 768px !important;
}
@media (min-width: 768px) {
.\!tw-container {
max-width: 768px !important;
}
}
@media (min-width: 1024px) {
.\!tw-container {
max-width: 1024px !important;
}
@media (min-width: 1024px) {
.\!tw-container {
max-width: 1024px !important;
}
}
@media (min-width: 1280px) {
.\!tw-container {
max-width: 1280px !important;
}
@media (min-width: 1280px) {
.\!tw-container {
max-width: 1280px !important;
}
}
@media (min-width: 1536px) {
.\!tw-container {
max-width: 1536px !important;
}
@media (min-width: 1536px) {
.\!tw-container {
max-width: 1536px !important;
}
}
.\!tw-font-bold {
font-weight: 700 !important;
}
.hover\:\!tw-text-center:hover {
text-align: center !important;
}
@media (min-width: 1024px) {
.lg\:\!tw-opacity-50 {
opacity: 0.5 !important;
}
.\!tw-font-bold {
font-weight: 700 !important;
}
@media (min-width: 1280px) {
.xl\:focus\:disabled\:\!tw-float-right:disabled:focus {
float: right !important;
}
.hover\:\!tw-text-center:hover {
text-align: center !important;
}
@media (min-width: 1024px) {
.lg\:\!tw-opacity-50 {
opacity: 0.5 !important;
}
}
@media (min-width: 1280px) {
.xl\:focus\:disabled\:\!tw-float-right:disabled:focus {
float: right !important;
}
}
`)
})
}
`)
})
})
+155 -157
View File
@@ -1,182 +1,180 @@
import { crosscheck, run, html, css } from './util/run'
import { run, html, css } from './util/run'
crosscheck(() => {
test('important modifier', () => {
let config = {
important: false,
darkMode: 'selector',
content: [
{
raw: html`
<div class="!container"></div>
<div class="!font-bold"></div>
<div class="hover:!text-center"></div>
<div class="lg:!opacity-50"></div>
<div class="xl:focus:disabled:!float-right"></div>
<div class="!custom-parent-5"></div>
<div class="btn !disabled"></div>
`,
},
],
corePlugins: { preflight: false },
plugins: [
function ({ theme, matchUtilities, addComponents }) {
matchUtilities(
{
'custom-parent': (value) => {
return {
'.custom-child': {
margin: value,
},
}
},
test('important modifier', () => {
let config = {
important: false,
darkMode: 'selector',
content: [
{
raw: html`
<div class="!container"></div>
<div class="!font-bold"></div>
<div class="hover:!text-center"></div>
<div class="lg:!opacity-50"></div>
<div class="xl:focus:disabled:!float-right"></div>
<div class="!custom-parent-5"></div>
<div class="btn !disabled"></div>
`,
},
],
corePlugins: { preflight: false },
plugins: [
function ({ theme, matchUtilities, addComponents }) {
matchUtilities(
{
'custom-parent': (value) => {
return {
'.custom-child': {
margin: value,
},
}
},
{ values: theme('spacing') }
)
addComponents({
'.btn': {
'&.disabled, &:disabled': {
color: 'gray',
},
},
{ values: theme('spacing') }
)
addComponents({
'.btn': {
'&.disabled, &:disabled': {
color: 'gray',
},
})
},
],
}
},
})
},
],
}
let input = css`
@tailwind components;
@tailwind utilities;
`
let input = css`
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.\!container {
width: 100% !important;
}
@media (min-width: 640px) {
.\!container {
width: 100% !important;
max-width: 640px !important;
}
@media (min-width: 640px) {
.\!container {
max-width: 640px !important;
}
}
@media (min-width: 768px) {
.\!container {
max-width: 768px !important;
}
@media (min-width: 768px) {
.\!container {
max-width: 768px !important;
}
}
@media (min-width: 1024px) {
.\!container {
max-width: 1024px !important;
}
@media (min-width: 1024px) {
.\!container {
max-width: 1024px !important;
}
}
@media (min-width: 1280px) {
.\!container {
max-width: 1280px !important;
}
@media (min-width: 1280px) {
.\!container {
max-width: 1280px !important;
}
}
@media (min-width: 1536px) {
.\!container {
max-width: 1536px !important;
}
@media (min-width: 1536px) {
.\!container {
max-width: 1536px !important;
}
}
.btn.disabled,
.btn:disabled {
color: gray;
}
.btn.\!disabled {
color: gray !important;
}
.\!font-bold {
font-weight: 700 !important;
}
.\!custom-parent-5 .custom-child {
margin: 1.25rem !important;
}
.hover\:\!text-center:hover {
text-align: center !important;
}
@media (min-width: 1024px) {
.lg\:\!opacity-50 {
opacity: 0.5 !important;
}
.btn.disabled,
.btn:disabled {
color: gray;
}
@media (min-width: 1280px) {
.xl\:focus\:disabled\:\!float-right:disabled:focus {
float: right !important;
}
.btn.\!disabled {
color: gray !important;
}
.\!font-bold {
font-weight: 700 !important;
}
.\!custom-parent-5 .custom-child {
margin: 1.25rem !important;
}
.hover\:\!text-center:hover {
text-align: center !important;
}
@media (min-width: 1024px) {
.lg\:\!opacity-50 {
opacity: 0.5 !important;
}
}
@media (min-width: 1280px) {
.xl\:focus\:disabled\:\!float-right:disabled:focus {
float: right !important;
}
}
`)
})
}
`)
})
})
test('the important modifier works on utilities using :where()', () => {
let config = {
content: [
{
raw: html` <div class="btn hover:btn !btn hover:focus:disabled:!btn"></div> `,
},
],
corePlugins: { preflight: false },
plugins: [
function ({ addComponents }) {
addComponents({
':where(.btn)': {
backgroundColor: '#00f',
},
})
},
],
}
test('the important modifier works on utilities using :where()', () => {
let config = {
content: [
{
raw: html` <div class="btn hover:btn !btn hover:focus:disabled:!btn"></div> `,
},
],
corePlugins: { preflight: false },
plugins: [
function ({ addComponents }) {
addComponents({
':where(.btn)': {
backgroundColor: '#00f',
},
})
},
],
}
let input = css`
@tailwind components;
@tailwind utilities;
`
let input = css`
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
:where(.\!btn) {
background-color: #00f !important;
}
:where(.btn) {
background-color: #00f;
}
:where(.hover\:btn:hover) {
background-color: #00f;
}
:where(.hover\:focus\:disabled\:\!btn:disabled:focus:hover) {
background-color: #00f !important;
}
`)
})
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
:where(.\!btn) {
background-color: #00f !important;
}
:where(.btn) {
background-color: #00f;
}
:where(.hover\:btn:hover) {
background-color: #00f;
}
:where(.hover\:focus\:disabled\:\!btn:disabled:focus:hover) {
background-color: #00f !important;
}
`)
})
})
test('the important modifier does not break keyframes', () => {
let config = {
content: [
{
raw: html` <div class="!animate-pulse"></div> `,
},
],
corePlugins: { preflight: false },
}
test('the important modifier does not break keyframes', () => {
let config = {
content: [
{
raw: html` <div class="!animate-pulse"></div> `,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind utilities;
`
let input = css`
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@keyframes pulse {
50% {
opacity: 0.5;
}
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@keyframes pulse {
50% {
opacity: 0.5;
}
}
.\!animate-pulse {
animation: 2s cubic-bezier(0.4, 0, 0.6, 1) infinite pulse !important;
}
`)
})
.\!animate-pulse {
animation: 2s cubic-bezier(0.4, 0, 0.6, 1) infinite pulse !important;
}
`)
})
})
+176 -185
View File
@@ -1,208 +1,199 @@
import { crosscheck, run, html, css, defaults } from './util/run'
import { run, html, css, defaults } from './util/run'
crosscheck(({ stable, oxide }) => {
test('important selector', () => {
let config = {
important: '#app',
darkMode: 'selector',
content: [
{
raw: html`
<div class="container"></div>
<div class="btn"></div>
<div class="animate-spin"></div>
<div class="custom-util"></div>
<div class="custom-component"></div>
<div class="custom-important-component"></div>
<div class="font-bold"></div>
<div class="md:hover:text-right"></div>
<div class="motion-safe:hover:text-center"></div>
<div class="dark:focus:text-left"></div>
<div class="group-hover:focus-within:text-left"></div>
<div class="rtl:active:text-center"></div>
<div class="dark:before:underline"></div>
<div class="hover:[&::file-selector-button]:rtl:dark:bg-black/100"></div>
`,
},
],
corePlugins: { preflight: false },
plugins: [
function ({ addComponents, addUtilities }) {
addComponents(
{
'.btn': {
button: 'yes',
},
test('important selector', () => {
let config = {
important: '#app',
darkMode: 'selector',
content: [
{
raw: html`
<div class="container"></div>
<div class="btn"></div>
<div class="animate-spin"></div>
<div class="custom-util"></div>
<div class="custom-component"></div>
<div class="custom-important-component"></div>
<div class="font-bold"></div>
<div class="md:hover:text-right"></div>
<div class="motion-safe:hover:text-center"></div>
<div class="dark:focus:text-left"></div>
<div class="group-hover:focus-within:text-left"></div>
<div class="rtl:active:text-center"></div>
<div class="dark:before:underline"></div>
<div class="hover:[&::file-selector-button]:rtl:dark:bg-black/100"></div>
`,
},
],
corePlugins: { preflight: false },
plugins: [
function ({ addComponents, addUtilities }) {
addComponents(
{
'.btn': {
button: 'yes',
},
{ respectImportant: true }
)
addComponents(
{
'@font-face': {
'font-family': 'Inter',
},
'@page': {
margin: '1cm',
},
},
{ respectImportant: true }
)
addComponents(
{
'@font-face': {
'font-family': 'Inter',
},
{ respectImportant: true }
)
addUtilities(
{
'.custom-util': {
button: 'no',
},
'@page': {
margin: '1cm',
},
{ respectImportant: false }
)
},
],
},
{ respectImportant: true }
)
addUtilities(
{
'.custom-util': {
button: 'no',
},
},
{ respectImportant: false }
)
},
],
}
let input = css`
@tailwind base;
@tailwind components;
@layer components {
.custom-component {
@apply font-bold;
}
.custom-important-component {
@apply text-center !important;
}
}
@tailwind utilities;
`
let input = css`
@tailwind base;
@tailwind components;
@layer components {
.custom-component {
@apply font-bold;
}
.custom-important-component {
@apply text-center !important;
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.container {
width: 100%;
}
@media (min-width: 640px) {
.container {
max-width: 640px;
}
}
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
@media (min-width: 768px) {
.container {
width: 100%;
max-width: 768px;
}
@media (min-width: 640px) {
.container {
max-width: 640px;
}
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
}
@media (min-width: 1536px) {
.container {
max-width: 1536px;
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
}
#app .btn {
button: yes;
}
@font-face {
font-family: Inter;
}
@page {
margin: 1cm;
}
.custom-component {
font-weight: 700;
}
.custom-important-component {
text-align: center !important;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
@media (min-width: 1536px) {
.container {
max-width: 1536px;
}
}
#app .btn {
button: yes;
}
@font-face {
font-family: Inter;
}
@page {
margin: 1cm;
}
.custom-component {
font-weight: 700;
}
.custom-important-component {
text-align: center !important;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
#app :is(.animate-spin) {
animation: 1s linear infinite spin;
}
#app :is(.font-bold) {
font-weight: 700;
}
.custom-util {
button: no;
}
#app :is(.group:hover .group-hover\:focus-within\:text-left:focus-within) {
text-align: left;
}
@media (prefers-reduced-motion: no-preference) {
#app :is(.motion-safe\:hover\:text-center:hover) {
text-align: center;
}
}
@media (min-width: 768px) {
#app :is(.md\:hover\:text-right:hover) {
text-align: right;
}
}
#app :is(.rtl\:active\:text-center:active:where([dir='rtl'], [dir='rtl'] *)) {
}
#app :is(.animate-spin) {
animation: 1s linear infinite spin;
}
#app :is(.font-bold) {
font-weight: 700;
}
.custom-util {
button: no;
}
#app :is(.group:hover .group-hover\:focus-within\:text-left:focus-within) {
text-align: left;
}
@media (prefers-reduced-motion: no-preference) {
#app :is(.motion-safe\:hover\:text-center:hover) {
text-align: center;
}
#app :is(.dark\:before\:underline:where(.dark, .dark *)):before {
content: var(--tw-content);
text-decoration-line: underline;
}
@media (min-width: 768px) {
#app :is(.md\:hover\:text-right:hover) {
text-align: right;
}
#app :is(.dark\:focus\:text-left:focus:where(.dark, .dark *)) {
text-align: left;
}
#app
:is(
.hover\:\[\&\:\:file-selector-button\]\:rtl\:dark\:bg-black\/100:where(
.dark,
.dark *
):where([dir='rtl'], [dir='rtl'] *)
)::file-selector-button:hover {
background-color: #000;
}
`)
})
}
#app :is(.rtl\:active\:text-center:active:where([dir='rtl'], [dir='rtl'] *)) {
text-align: center;
}
#app :is(.dark\:before\:underline:where(.dark, .dark *)):before {
content: var(--tw-content);
text-decoration-line: underline;
}
#app :is(.dark\:focus\:text-left:focus:where(.dark, .dark *)) {
text-align: left;
}
#app
:is(
.hover\:\[\&\:\:file-selector-button\]\:rtl\:dark\:bg-black\/100:where(
.dark,
.dark *
):where([dir='rtl'], [dir='rtl'] *)
)::file-selector-button:hover {
background-color: #000;
}
`)
})
})
test('pseudo-elements are appended after the `:is()`', () => {
let config = {
important: '#app',
darkMode: 'selector',
content: [
{
raw: html` <div class="dark:before:bg-black"></div> `,
},
],
corePlugins: { preflight: false },
}
test('pseudo-elements are appended after the `:is()`', () => {
let config = {
important: '#app',
darkMode: 'selector',
content: [
{
raw: html` <div class="dark:before:bg-black"></div> `,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
stable.expect(result.css).toMatchFormattedCss(css`
${defaults}
#app .dark\:before\:bg-black:where(.dark, .dark *)::before {
content: var(--tw-content);
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`)
oxide.expect(result.css).toMatchFormattedCss(css`
${defaults}
#app .dark\:before\:bg-black:where(.dark, .dark *)::before {
content: var(--tw-content);
background-color: #000;
}
`)
})
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
#app .dark\:before\:bg-black:where(.dark, .dark *)::before {
content: var(--tw-content);
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
`)
})
})
+719 -1285
View File
File diff suppressed because it is too large Load Diff
+262 -264
View File
@@ -1,321 +1,319 @@
import { crosscheck, run, html, css, defaults } from './util/run'
import { run, html, css, defaults } from './util/run'
crosscheck(() => {
test('custom user-land utilities', () => {
let config = {
content: [
{
raw: html`<div
class="focus:hover:align-chocolate align-banana hover:align-banana uppercase"
></div>`,
},
],
corePlugins: { preflight: false },
theme: {},
plugins: [],
test('custom user-land utilities', () => {
let config = {
content: [
{
raw: html`<div
class="focus:hover:align-chocolate align-banana hover:align-banana uppercase"
></div>`,
},
],
corePlugins: { preflight: false },
theme: {},
plugins: [],
}
let input = css`
@layer utilities {
.align-banana {
text-align: banana;
}
}
let input = css`
@layer utilities {
.align-banana {
text-align: banana;
}
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer utilities {
.align-chocolate {
text-align: chocolate;
}
}
`
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer utilities {
.align-chocolate {
text-align: chocolate;
}
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.uppercase {
text-transform: uppercase;
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.uppercase {
text-transform: uppercase;
}
.align-banana,
.hover\:align-banana:hover {
text-align: banana;
}
.focus\:hover\:align-chocolate:hover:focus {
text-align: chocolate;
}
`)
})
.align-banana,
.hover\:align-banana:hover {
text-align: banana;
}
.focus\:hover\:align-chocolate:hover:focus {
text-align: chocolate;
}
`)
})
})
test('comments can be used inside layers without crashing', () => {
let config = {
content: [
{
raw: html`<div class="important-utility important-component"></div>`,
},
],
corePlugins: { preflight: false },
theme: {},
plugins: [],
test('comments can be used inside layers without crashing', () => {
let config = {
content: [
{
raw: html`<div class="important-utility important-component"></div>`,
},
],
corePlugins: { preflight: false },
theme: {},
plugins: [],
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
/* Important base */
div {
background-color: #bada55;
}
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
/* Important base */
div {
background-color: #bada55;
}
@layer utilities {
/* Important utility */
.important-utility {
text-align: banana;
}
}
@layer utilities {
/* Important utility */
@layer components {
/* Important component */
.important-component {
text-align: banana;
}
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
div {
background-color: #bada55;
}
${defaults}
.important-component,
.important-utility {
text-align: banana;
}
text-align: banana;
}
@layer components {
/* Important component */
.important-component {
text-align: banana;
}
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
div {
background-color: #bada55;
}
${defaults}
.important-component,
.important-utility {
text-align: banana;
}
`)
})
`)
})
})
test('comments can be used inside layers (with important) without crashing', () => {
let config = {
important: true,
content: [
{
raw: html`<div class="important-utility important-component"></div>`,
},
],
corePlugins: { preflight: false },
theme: {},
plugins: [],
test('comments can be used inside layers (with important) without crashing', () => {
let config = {
important: true,
content: [
{
raw: html`<div class="important-utility important-component"></div>`,
},
],
corePlugins: { preflight: false },
theme: {},
plugins: [],
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
/* Important base */
div {
background-color: #bada55;
}
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
/* Important base */
div {
background-color: #bada55;
}
@layer utilities {
/* Important utility */
.important-utility {
text-align: banana;
}
}
@layer utilities {
/* Important utility */
.important-utility {
text-align: banana;
}
@layer components {
/* Important component */
.important-component {
text-align: banana;
}
}
`
@layer components {
/* Important component */
.important-component {
text-align: banana;
}
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
div {
background-color: #bada55;
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
div {
background-color: #bada55;
}
${defaults}
.important-component {
text-align: banana;
}
.important-utility {
text-align: banana !important;
}
`)
})
${defaults}
.important-component {
text-align: banana;
}
.important-utility {
text-align: banana !important;
}
`)
})
})
test('layers are grouped and inserted at the matching @tailwind rule', () => {
let config = {
content: [
{
raw: html`<div class="input btn card float-squirrel align-banana align-sandwich"></div>`,
},
],
plugins: [
function ({ addBase, addComponents, addUtilities }) {
addBase({ body: { margin: 0 } })
test('layers are grouped and inserted at the matching @tailwind rule', () => {
let config = {
content: [
{
raw: html`<div class="input btn card float-squirrel align-banana align-sandwich"></div>`,
},
],
plugins: [
function ({ addBase, addComponents, addUtilities }) {
addBase({ body: { margin: 0 } })
addComponents({
'.input': { background: 'white' },
})
addComponents({
'.input': { background: 'white' },
})
addUtilities({
'.float-squirrel': { float: 'squirrel' },
})
},
],
corePlugins: { preflight: false },
addUtilities({
'.float-squirrel': { float: 'squirrel' },
})
},
],
corePlugins: { preflight: false },
}
let input = css`
@layer vanilla {
strong {
font-weight: medium;
}
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn {
background: blue;
}
}
@layer utilities {
.align-banana {
text-align: banana;
}
}
@layer base {
h1 {
font-weight: bold;
}
}
@layer components {
.card {
border-radius: 12px;
}
}
@layer base {
p {
font-weight: normal;
}
}
@layer utilities {
.align-sandwich {
text-align: sandwich;
}
}
@layer chocolate {
a {
text-decoration: underline;
}
}
`
expect.assertions(2)
return run(input, config).then((result) => {
expect(result.warnings().length).toBe(0)
expect(result.css).toMatchFormattedCss(css`
@layer vanilla {
strong {
font-weight: medium;
}
}
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn {
background: blue;
}
body {
margin: 0;
}
@layer utilities {
.align-banana {
text-align: banana;
}
h1 {
font-weight: bold;
}
@layer base {
h1 {
font-weight: bold;
}
p {
font-weight: normal;
}
@layer components {
.card {
border-radius: 12px;
}
${defaults}
.input {
background: #fff;
}
@layer base {
p {
font-weight: normal;
}
.btn {
background: #00f;
}
@layer utilities {
.align-sandwich {
text-align: sandwich;
}
.card {
border-radius: 12px;
}
.float-squirrel {
float: squirrel;
}
.align-banana {
text-align: banana;
}
.align-sandwich {
text-align: sandwich;
}
@layer chocolate {
a {
text-decoration: underline;
}
}
`
expect.assertions(2)
`)
})
})
return run(input, config).then((result) => {
expect(result.warnings().length).toBe(0)
expect(result.css).toMatchFormattedCss(css`
@layer vanilla {
strong {
font-weight: medium;
}
}
body {
margin: 0;
}
h1 {
font-weight: bold;
}
p {
font-weight: normal;
}
${defaults}
.input {
background: #fff;
}
.btn {
background: #00f;
}
.card {
border-radius: 12px;
}
.float-squirrel {
float: squirrel;
}
.align-banana {
text-align: banana;
}
.align-sandwich {
text-align: sandwich;
}
@layer chocolate {
a {
text-decoration: underline;
}
}
`)
})
})
it('should keep `@supports` rules inside `@layer`s', () => {
let config = {
content: [{ raw: html`<div class="test"></div>` }],
plugins: [],
}
it('should keep `@supports` rules inside `@layer`s', () => {
let config = {
content: [{ raw: html`<div class="test"></div>` }],
plugins: [],
}
let input = css`
@tailwind utilities;
let input = css`
@tailwind utilities;
@layer utilities {
.test {
--tw-test: 1;
}
@layer utilities {
@supports (backdrop-filter: blur(1px)) {
.test {
--tw-test: 1;
--tw-test: 0.9;
}
}
}
`
@supports (backdrop-filter: blur(1px)) {
.test {
--tw-test: 0.9;
}
}
return run(input, config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
.test {
--tw-test: 1;
}
`
return run(input, config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
@supports (backdrop-filter: blur(1px)) {
.test {
--tw-test: 1;
--tw-test: 0.9;
}
@supports (backdrop-filter: blur(1px)) {
.test {
--tw-test: 0.9;
}
}
`)
})
}
`)
})
})
+62 -64
View File
@@ -1,81 +1,79 @@
import { crosscheck, run, html, css } from './util/run'
import { run, html, css } from './util/run'
crosscheck(() => {
test('using @layer without @tailwind', async () => {
let config = {
content: [{ raw: html`<div class="foo"></div>` }],
}
test('using @layer without @tailwind', async () => {
let config = {
content: [{ raw: html`<div class="foo"></div>` }],
}
let input = css`
@layer components {
.foo {
color: black;
}
let input = css`
@layer components {
.foo {
color: black;
}
`
await expect(run(input, config)).rejects.toThrowError(
'`@layer components` is used but no matching `@tailwind components` directive is present.'
)
})
test('using @responsive without @tailwind', async () => {
let config = {
content: [{ raw: html`<div class="foo"></div>` }],
}
`
let input = css`
@responsive {
.foo {
color: black;
}
await expect(run(input, config)).rejects.toThrowError(
'`@layer components` is used but no matching `@tailwind components` directive is present.'
)
})
test('using @responsive without @tailwind', async () => {
let config = {
content: [{ raw: html`<div class="foo"></div>` }],
}
let input = css`
@responsive {
.foo {
color: black;
}
`
await expect(run(input, config)).rejects.toThrowError(
'`@responsive` is used but `@tailwind utilities` is missing.'
)
})
test('using @variants without @tailwind', async () => {
let config = {
content: [{ raw: html`<div class="foo"></div>` }],
}
`
let input = css`
@variants hover {
.foo {
color: black;
}
await expect(run(input, config)).rejects.toThrowError(
'`@responsive` is used but `@tailwind utilities` is missing.'
)
})
test('using @variants without @tailwind', async () => {
let config = {
content: [{ raw: html`<div class="foo"></div>` }],
}
let input = css`
@variants hover {
.foo {
color: black;
}
`
await expect(run(input, config)).rejects.toThrowError(
'`@variants` is used but `@tailwind utilities` is missing.'
)
})
test('non-Tailwind @layer rules are okay', async () => {
let config = {
content: [{ raw: html`<div class="foo"></div>` }],
}
`
let input = css`
await expect(run(input, config)).rejects.toThrowError(
'`@variants` is used but `@tailwind utilities` is missing.'
)
})
test('non-Tailwind @layer rules are okay', async () => {
let config = {
content: [{ raw: html`<div class="foo"></div>` }],
}
let input = css`
@layer custom {
.foo {
color: black;
}
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@layer custom {
.foo {
color: black;
color: #000;
}
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@layer custom {
.foo {
color: #000;
}
}
`)
})
`)
})
})
+74 -78
View File
@@ -1,86 +1,82 @@
import { crosscheck, run, html, css, defaults } from './util/run'
import { run, html, css, defaults } from './util/run'
crosscheck(() => {
it('should be possible to matchComponents', () => {
let config = {
content: [
{
raw: html`<div class="card-[#0088cc] hover:card-[#f0f]">
<div class="card-header font-bold"></div>
<div class="shadow"></div>
<div class="card-footer text-center"></div>
</div>`,
},
],
corePlugins: {
preflight: false,
it('should be possible to matchComponents', () => {
let config = {
content: [
{
raw: html`<div class="card-[#0088cc] hover:card-[#f0f]">
<div class="card-header font-bold"></div>
<div class="shadow"></div>
<div class="card-footer text-center"></div>
</div>`,
},
plugins: [
function ({ matchComponents }) {
matchComponents({
card: (value) => {
return [
{ color: value },
{
'.card-header': {
borderTopWidth: 3,
borderTopColor: value,
},
],
corePlugins: {
preflight: false,
},
plugins: [
function ({ matchComponents }) {
matchComponents({
card: (value) => {
return [
{ color: value },
{
'.card-header': {
borderTopWidth: 3,
borderTopColor: value,
},
{
'.card-footer': {
borderBottomWidth: 3,
borderBottomColor: value,
},
},
{
'.card-footer': {
borderBottomWidth: 3,
borderBottomColor: value,
},
]
},
})
},
],
}
},
]
},
})
},
],
}
return run('@tailwind base; @tailwind components; @tailwind utilities', config).then(
(result) => {
return expect(result.css).toMatchFormattedCss(css`
${defaults}
.card-\[\#0088cc\] {
color: #08c;
}
.card-\[\#0088cc\] .card-header {
border-top-width: 3px;
border-top-color: #08c;
}
.card-\[\#0088cc\] .card-footer {
border-bottom-width: 3px;
border-bottom-color: #08c;
}
.text-center {
text-align: center;
}
.font-bold {
font-weight: 700;
}
.shadow {
--tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
0 1px 2px -1px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.hover\:card-\[\#f0f\]:hover {
color: #f0f;
}
.hover\:card-\[\#f0f\]:hover .card-header {
border-top-width: 3px;
border-top-color: #f0f;
}
.hover\:card-\[\#f0f\]:hover .card-footer {
border-bottom-width: 3px;
border-bottom-color: #f0f;
}
`)
return run('@tailwind base; @tailwind components; @tailwind utilities', config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
${defaults}
.card-\[\#0088cc\] {
color: #08c;
}
)
.card-\[\#0088cc\] .card-header {
border-top-width: 3px;
border-top-color: #08c;
}
.card-\[\#0088cc\] .card-footer {
border-bottom-width: 3px;
border-bottom-color: #08c;
}
.text-center {
text-align: center;
}
.font-bold {
font-weight: 700;
}
.shadow {
--tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
0 1px 2px -1px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.hover\:card-\[\#f0f\]:hover {
color: #f0f;
}
.hover\:card-\[\#f0f\]:hover .card-header {
border-top-width: 3px;
border-top-color: #f0f;
}
.hover\:card-\[\#f0f\]:hover .card-footer {
border-bottom-width: 3px;
border-bottom-color: #f0f;
}
`)
})
})
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+26 -29
View File
@@ -1,32 +1,29 @@
import { elementSelectorParser } from '../src/lib/resolveDefaultsAtRules'
import { crosscheck } from './util/run'
crosscheck(() => {
it.each`
before | after
${'*'} | ${'*'}
${'*:hover'} | ${'*'}
${'* > *'} | ${'* > *'}
${'.foo'} | ${'.foo'}
${'.foo:hover'} | ${'.foo'}
${'.foo:focus:hover'} | ${'.foo'}
${'li:first-child'} | ${'li'}
${'li:before'} | ${'li:before'}
${'li::before'} | ${'li::before'}
${'#app .foo'} | ${'.foo'}
${'#app'} | ${'[id=app]'}
${'#app.other'} | ${'.other'}
${'input[type="text"]'} | ${'[type="text"]'}
${'input[type="text"].foo'} | ${'.foo'}
${'.group .group\\:foo'} | ${'.group\\:foo'}
${'.group:hover .group-hover\\:foo'} | ${'.group-hover\\:foo'}
${'.owl > * + *'} | ${'.owl > *'}
${'.owl > :not([hidden]) + :not([hidden])'} | ${'.owl > *'}
${'.group:hover .group-hover\\:owl > :not([hidden]) + :not([hidden])'} | ${'.group-hover\\:owl > *'}
${'.peer:first-child ~ .peer-first\\:shadow-md'} | ${'.peer-first\\:shadow-md'}
${'.whats ~ .next > span:hover'} | ${'span'}
${'.foo .bar ~ .baz > .next > span > article:hover'} | ${'article'}
`('should generate "$after" from "$before"', ({ before, after }) => {
expect(elementSelectorParser.transformSync(before).join(', ')).toEqual(after)
})
it.each`
before | after
${'*'} | ${'*'}
${'*:hover'} | ${'*'}
${'* > *'} | ${'* > *'}
${'.foo'} | ${'.foo'}
${'.foo:hover'} | ${'.foo'}
${'.foo:focus:hover'} | ${'.foo'}
${'li:first-child'} | ${'li'}
${'li:before'} | ${'li:before'}
${'li::before'} | ${'li::before'}
${'#app .foo'} | ${'.foo'}
${'#app'} | ${'[id=app]'}
${'#app.other'} | ${'.other'}
${'input[type="text"]'} | ${'[type="text"]'}
${'input[type="text"].foo'} | ${'.foo'}
${'.group .group\\:foo'} | ${'.group\\:foo'}
${'.group:hover .group-hover\\:foo'} | ${'.group-hover\\:foo'}
${'.owl > * + *'} | ${'.owl > *'}
${'.owl > :not([hidden]) + :not([hidden])'} | ${'.owl > *'}
${'.group:hover .group-hover\\:owl > :not([hidden]) + :not([hidden])'} | ${'.group-hover\\:owl > *'}
${'.peer:first-child ~ .peer-first\\:shadow-md'} | ${'.peer-first\\:shadow-md'}
${'.whats ~ .next > span:hover'} | ${'span'}
${'.foo .bar ~ .baz > .next > span > article:hover'} | ${'article'}
`('should generate "$after" from "$before"', ({ before, after }) => {
expect(elementSelectorParser.transformSync(before).join(', ')).toEqual(after)
})
+78 -80
View File
@@ -1,95 +1,93 @@
import selectorParser from 'postcss-selector-parser'
import { crosscheck, run, html, css } from './util/run'
import { run, html, css } from './util/run'
crosscheck(() => {
test('modify selectors', () => {
let config = {
darkMode: 'selector',
content: [
{
raw: html`
<div class="font-bold"></div>
<div class="foo:font-bold"></div>
<div class="foo:hover:font-bold"></div>
<div class="sm:foo:font-bold"></div>
<div class="md:foo:focus:font-bold"></div>
<div class="markdown">
<p>Lorem ipsum dolor sit amet...</p>
</div>
<div class="foo:markdown">
<p>Lorem ipsum dolor sit amet...</p>
</div>
<div class="foo:visited:markdown">
<p>Lorem ipsum dolor sit amet...</p>
</div>
<div class="lg:foo:disabled:markdown">
<p>Lorem ipsum dolor sit amet...</p>
</div>
`,
},
],
corePlugins: { preflight: false },
theme: {},
plugins: [
function ({ addVariant }) {
addVariant('foo', ({ modifySelectors, separator }) => {
modifySelectors(({ selector }) => {
return selectorParser((selectors) => {
selectors.walkClasses((classNode) => {
classNode.value = `foo${separator}${classNode.value}`
classNode.parent.insertBefore(classNode, selectorParser().astSync(`.foo `))
})
}).processSync(selector)
})
test('modify selectors', () => {
let config = {
darkMode: 'selector',
content: [
{
raw: html`
<div class="font-bold"></div>
<div class="foo:font-bold"></div>
<div class="foo:hover:font-bold"></div>
<div class="sm:foo:font-bold"></div>
<div class="md:foo:focus:font-bold"></div>
<div class="markdown">
<p>Lorem ipsum dolor sit amet...</p>
</div>
<div class="foo:markdown">
<p>Lorem ipsum dolor sit amet...</p>
</div>
<div class="foo:visited:markdown">
<p>Lorem ipsum dolor sit amet...</p>
</div>
<div class="lg:foo:disabled:markdown">
<p>Lorem ipsum dolor sit amet...</p>
</div>
`,
},
],
corePlugins: { preflight: false },
theme: {},
plugins: [
function ({ addVariant }) {
addVariant('foo', ({ modifySelectors, separator }) => {
modifySelectors(({ selector }) => {
return selectorParser((selectors) => {
selectors.walkClasses((classNode) => {
classNode.value = `foo${separator}${classNode.value}`
classNode.parent.insertBefore(classNode, selectorParser().astSync(`.foo `))
})
}).processSync(selector)
})
},
],
})
},
],
}
let input = css`
@tailwind components;
@tailwind utilities;
@layer components {
.markdown > p {
margin-top: 12px;
}
}
`
let input = css`
@tailwind components;
@tailwind utilities;
@layer components {
.markdown > p {
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.markdown > p {
margin-top: 12px;
}
.font-bold {
font-weight: 700;
}
.foo .foo\:markdown > p,
.foo .foo\:visited\:markdown:visited > p {
margin-top: 12px;
}
@media (min-width: 1024px) {
.foo .lg\:foo\:disabled\:markdown:disabled > p {
margin-top: 12px;
}
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.markdown > p {
margin-top: 12px;
}
.font-bold {
.foo .foo\:font-bold,
.foo .foo\:hover\:font-bold:hover {
font-weight: 700;
}
@media (min-width: 640px) {
.foo .sm\:foo\:font-bold {
font-weight: 700;
}
.foo .foo\:markdown > p,
.foo .foo\:visited\:markdown:visited > p {
margin-top: 12px;
}
@media (min-width: 1024px) {
.foo .lg\:foo\:disabled\:markdown:disabled > p {
margin-top: 12px;
}
}
.foo .foo\:font-bold,
.foo .foo\:hover\:font-bold:hover {
}
@media (min-width: 768px) {
.foo .md\:foo\:focus\:font-bold:focus {
font-weight: 700;
}
@media (min-width: 640px) {
.foo .sm\:foo\:font-bold {
font-weight: 700;
}
}
@media (min-width: 768px) {
.foo .md\:foo\:focus\:font-bold:focus {
font-weight: 700;
}
}
`)
})
}
`)
})
})
+41 -43
View File
@@ -1,54 +1,52 @@
import path from 'path'
import postcss from 'postcss'
import { crosscheck, run, html, css } from './util/run'
import { run, html, css } from './util/run'
crosscheck(() => {
function pluginThatMutatesRules() {
return (root) => {
root.walkRules((rule) => {
rule.nodes
.filter((node) => node.prop === 'background-image')
.forEach((node) => {
node.value = 'url("./bar.png")'
})
function pluginThatMutatesRules() {
return (root) => {
root.walkRules((rule) => {
rule.nodes
.filter((node) => node.prop === 'background-image')
.forEach((node) => {
node.value = 'url("./bar.png")'
})
return rule
})
}
return rule
})
}
}
test('plugins mutating rules after tailwind doesnt break it', async () => {
let config = {
content: [{ raw: html`<div class="bg-foo"></div>` }],
theme: {
backgroundImage: {
foo: 'url("./foo.png")',
},
},
plugins: [],
}
test('plugins mutating rules after tailwind doesnt break it', async () => {
let config = {
content: [{ raw: html`<div class="bg-foo"></div>` }],
theme: {
backgroundImage: {
foo: 'url("./foo.png")',
},
},
plugins: [],
}
function checkResult(result) {
expect(result.css).toMatchFormattedCss(css`
.bg-foo {
background-image: url('./foo.png');
}
`)
}
function checkResult(result) {
expect(result.css).toMatchFormattedCss(css`
.bg-foo {
background-image: url('./foo.png');
}
`)
}
// Verify the first run produces the expected result
let firstRun = await run('@tailwind utilities', config)
checkResult(firstRun)
// Verify the first run produces the expected result
let firstRun = await run('@tailwind utilities', config)
checkResult(firstRun)
// Outside of the context of tailwind jit more postcss plugins may operate on the AST:
// In this case we have a plugin that mutates rules directly
await postcss([pluginThatMutatesRules()]).process(firstRun, {
from: path.resolve(__filename),
})
// Verify subsequent runs don't produce mutated rules
let secondRun = await run('@tailwind utilities', config)
checkResult(secondRun)
// Outside of the context of tailwind jit more postcss plugins may operate on the AST:
// In this case we have a plugin that mutates rules directly
await postcss([pluginThatMutatesRules()]).process(firstRun, {
from: path.resolve(__filename),
})
// Verify subsequent runs don't produce mutated rules
let secondRun = await run('@tailwind utilities', config)
checkResult(secondRun)
})
+10 -13
View File
@@ -1,17 +1,14 @@
import negateValue from '../src/util/negateValue'
import { crosscheck } from './util/run'
crosscheck(() => {
test('it negates numeric CSS values', () => {
expect(negateValue('5')).toEqual('-5')
expect(negateValue('10px')).toEqual('-10px')
expect(negateValue('18rem')).toEqual('-18rem')
expect(negateValue('-10')).toEqual('10')
expect(negateValue('-7ch')).toEqual('7ch')
})
test('it negates numeric CSS values', () => {
expect(negateValue('5')).toEqual('-5')
expect(negateValue('10px')).toEqual('-10px')
expect(negateValue('18rem')).toEqual('-18rem')
expect(negateValue('-10')).toEqual('10')
expect(negateValue('-7ch')).toEqual('7ch')
})
test('values that cannot be negated become undefined', () => {
expect(negateValue('auto')).toBeUndefined()
expect(negateValue('cover')).toBeUndefined()
})
test('values that cannot be negated become undefined', () => {
expect(negateValue('auto')).toBeUndefined()
expect(negateValue('cover')).toBeUndefined()
})
+21 -23
View File
@@ -1,29 +1,27 @@
import * as path from 'path'
import { crosscheck, run, css, defaults } from './util/run'
import { run, css, defaults } from './util/run'
crosscheck(() => {
it('should be possible to use negated content patterns', () => {
let config = {
content: [
path.resolve(__dirname, './negated-content-*.test.html'),
'!' + path.resolve(__dirname, './negated-content-ignore.test.html'),
],
corePlugins: { preflight: false },
}
it('should be possible to use negated content patterns', () => {
let config = {
content: [
path.resolve(__dirname, './negated-content-*.test.html'),
'!' + path.resolve(__dirname, './negated-content-ignore.test.html'),
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.uppercase {
text-transform: uppercase;
}
`)
})
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.uppercase {
text-transform: uppercase;
}
`)
})
})
+357 -361
View File
@@ -1,366 +1,362 @@
import { crosscheck, run, html, css } from './util/run'
import { run, html, css } from './util/run'
crosscheck(() => {
test('using a negative prefix with a negative scale value', () => {
let config = {
content: [{ raw: html`<div class="mt-2 -mt-2"></div>` }],
theme: {
margin: {
2: '8px',
'-2': '-4px',
},
test('using a negative prefix with a negative scale value', () => {
let config = {
content: [{ raw: html`<div class="mt-2 -mt-2"></div>` }],
theme: {
margin: {
2: '8px',
'-2': '-4px',
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt-2 {
margin-top: -4px;
}
.mt-2 {
margin-top: 8px;
}
`)
})
})
test('using a negative scale value with a plugin that does not support dynamic negative values', () => {
let config = {
content: [{ raw: html`<div class="-opacity-50"></div>` }],
theme: {
opacity: {
'-50': '0.5',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-opacity-50 {
opacity: 0.5;
}
`)
})
})
test('using a negative prefix without a negative scale value', () => {
let config = {
content: [{ raw: html`<div class="mt-5 -mt-5"></div>` }],
theme: {
margin: {
5: '20px',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt-5 {
margin-top: -20px;
}
.mt-5 {
margin-top: 20px;
}
`)
})
})
test('being an asshole', () => {
let config = {
content: [{ raw: html`<div class="-mt-[10px]"></div>` }],
theme: {
margin: {
'-[10px]': '55px',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt-\[10px\] {
margin-top: 55px;
}
`)
})
})
test('being a real asshole', () => {
let config = {
content: [{ raw: html`<div class="-mt-[10px]"></div>` }],
theme: {
margin: {
'[10px]': '55px',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt-\[10px\] {
margin-top: -55px;
}
`)
})
})
test('a value that includes a variable', () => {
let config = {
content: [{ raw: html`<div class="mt-5 -mt-5"></div>` }],
theme: {
margin: {
5: 'var(--sizing-5)',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt-5 {
margin-top: calc(var(--sizing-5) * -1);
}
.mt-5 {
margin-top: var(--sizing-5);
}
`)
})
})
test('a value that includes a calc', () => {
let config = {
content: [{ raw: html`<div class="mt-5 -mt-5"></div>` }],
theme: {
margin: {
5: 'calc(52px * -3)',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt-5 {
margin-top: 156px;
}
.mt-5 {
margin-top: -156px;
}
`)
})
})
test('a value that includes min/max/clamp functions', () => {
let config = {
content: [
{ raw: html`<div class="mt-min -mt-min mt-max -mt-max mt-clamp -mt-clamp"></div>` },
],
theme: {
margin: {
min: 'min(100vmin, 3rem)',
max: 'max(100vmax, 3rem)',
clamp: 'clamp(1rem, 100vh, 3rem)',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt-clamp {
margin-top: calc(-1 * clamp(1rem, 100vh, 3rem));
}
.-mt-max {
margin-top: calc(-1 * max(100vmax, 3rem));
}
.-mt-min {
margin-top: calc(-1 * min(100vmin, 3rem));
}
.mt-clamp {
margin-top: clamp(1rem, 100vh, 3rem);
}
.mt-max {
margin-top: max(100vmax, 3rem);
}
.mt-min {
margin-top: min(100vmin, 3rem);
}
`)
})
})
test('a keyword value', () => {
let config = {
content: [{ raw: html`<div class="-mt-auto mt-auto"></div>` }],
theme: {
margin: {
auto: 'auto',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.mt-auto {
margin-top: auto;
}
`)
})
})
test('a zero value', () => {
let config = {
content: [{ raw: html`<div class="mt-0 -mt-0"></div>` }],
theme: {
margin: {
0: '0',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt-0,
.mt-0 {
margin-top: 0;
}
`)
})
})
test('a color', () => {
let config = {
content: [{ raw: html`<div class="-bg-red"></div>` }],
theme: {
colors: {
red: 'red',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css``)
})
})
test('arbitrary values', () => {
let config = {
content: [{ raw: html`<div class="-mt-[10px]"></div>` }],
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt-\[10px\] {
margin-top: -10px;
}
`)
})
})
test('negating a negative scale value', () => {
let config = {
content: [{ raw: html`<div class="-mt-weird"></div>` }],
theme: {
margin: {
weird: '-15px',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt-weird {
margin-top: 15px;
}
`)
})
})
test('negating a default value', () => {
let config = {
content: [{ raw: html`<div class="-mt"></div>` }],
theme: {
margin: {
DEFAULT: '15px',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt {
margin-top: -15px;
}
`)
})
})
test('using a negative prefix with a negative default scale value', () => {
let config = {
content: [{ raw: html`<div class="mt -mt"></div>` }],
theme: {
margin: {
DEFAULT: '8px',
'-DEFAULT': '-4px',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt {
margin-top: -4px;
}
.mt {
margin-top: 8px;
}
`)
})
})
test('negating a default value with a configured prefix', () => {
let config = {
prefix: 'tw-',
content: [{ raw: html`<div class="tw--mt"></div>` }],
theme: {
margin: {
DEFAULT: '15px',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.tw--mt {
margin-top: -15px;
}
`)
})
})
test('arbitrary value keywords should be ignored', () => {
let config = {
content: [{ raw: html`<div class="-mt-[auto]"></div>` }],
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css``)
})
})
// This is a weird test but it used to crash because the negative prefix + variant used to cause an undefined utility to be generated
test('addUtilities without negative prefix + variant + negative prefix in content should not crash', async () => {
let config = {
content: [{ raw: html`<div class="hover:-top-lg"></div>` }],
plugins: [
({ addUtilities }) => {
addUtilities({
'.top-lg': {
top: '6rem',
},
})
},
],
}
let result = await run('@tailwind utilities', config)
expect(result.css).toMatchCss(css``)
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt-2 {
margin-top: -4px;
}
.mt-2 {
margin-top: 8px;
}
`)
})
})
test('using a negative scale value with a plugin that does not support dynamic negative values', () => {
let config = {
content: [{ raw: html`<div class="-opacity-50"></div>` }],
theme: {
opacity: {
'-50': '0.5',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-opacity-50 {
opacity: 0.5;
}
`)
})
})
test('using a negative prefix without a negative scale value', () => {
let config = {
content: [{ raw: html`<div class="mt-5 -mt-5"></div>` }],
theme: {
margin: {
5: '20px',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt-5 {
margin-top: -20px;
}
.mt-5 {
margin-top: 20px;
}
`)
})
})
test('being an asshole', () => {
let config = {
content: [{ raw: html`<div class="-mt-[10px]"></div>` }],
theme: {
margin: {
'-[10px]': '55px',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt-\[10px\] {
margin-top: 55px;
}
`)
})
})
test('being a real asshole', () => {
let config = {
content: [{ raw: html`<div class="-mt-[10px]"></div>` }],
theme: {
margin: {
'[10px]': '55px',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt-\[10px\] {
margin-top: -55px;
}
`)
})
})
test('a value that includes a variable', () => {
let config = {
content: [{ raw: html`<div class="mt-5 -mt-5"></div>` }],
theme: {
margin: {
5: 'var(--sizing-5)',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt-5 {
margin-top: calc(var(--sizing-5) * -1);
}
.mt-5 {
margin-top: var(--sizing-5);
}
`)
})
})
test('a value that includes a calc', () => {
let config = {
content: [{ raw: html`<div class="mt-5 -mt-5"></div>` }],
theme: {
margin: {
5: 'calc(52px * -3)',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt-5 {
margin-top: 156px;
}
.mt-5 {
margin-top: -156px;
}
`)
})
})
test('a value that includes min/max/clamp functions', () => {
let config = {
content: [{ raw: html`<div class="mt-min -mt-min mt-max -mt-max mt-clamp -mt-clamp"></div>` }],
theme: {
margin: {
min: 'min(100vmin, 3rem)',
max: 'max(100vmax, 3rem)',
clamp: 'clamp(1rem, 100vh, 3rem)',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt-clamp {
margin-top: calc(-1 * clamp(1rem, 100vh, 3rem));
}
.-mt-max {
margin-top: calc(-1 * max(100vmax, 3rem));
}
.-mt-min {
margin-top: calc(-1 * min(100vmin, 3rem));
}
.mt-clamp {
margin-top: clamp(1rem, 100vh, 3rem);
}
.mt-max {
margin-top: max(100vmax, 3rem);
}
.mt-min {
margin-top: min(100vmin, 3rem);
}
`)
})
})
test('a keyword value', () => {
let config = {
content: [{ raw: html`<div class="-mt-auto mt-auto"></div>` }],
theme: {
margin: {
auto: 'auto',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.mt-auto {
margin-top: auto;
}
`)
})
})
test('a zero value', () => {
let config = {
content: [{ raw: html`<div class="mt-0 -mt-0"></div>` }],
theme: {
margin: {
0: '0',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt-0,
.mt-0 {
margin-top: 0;
}
`)
})
})
test('a color', () => {
let config = {
content: [{ raw: html`<div class="-bg-red"></div>` }],
theme: {
colors: {
red: 'red',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css``)
})
})
test('arbitrary values', () => {
let config = {
content: [{ raw: html`<div class="-mt-[10px]"></div>` }],
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt-\[10px\] {
margin-top: -10px;
}
`)
})
})
test('negating a negative scale value', () => {
let config = {
content: [{ raw: html`<div class="-mt-weird"></div>` }],
theme: {
margin: {
weird: '-15px',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt-weird {
margin-top: 15px;
}
`)
})
})
test('negating a default value', () => {
let config = {
content: [{ raw: html`<div class="-mt"></div>` }],
theme: {
margin: {
DEFAULT: '15px',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt {
margin-top: -15px;
}
`)
})
})
test('using a negative prefix with a negative default scale value', () => {
let config = {
content: [{ raw: html`<div class="mt -mt"></div>` }],
theme: {
margin: {
DEFAULT: '8px',
'-DEFAULT': '-4px',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.-mt {
margin-top: -4px;
}
.mt {
margin-top: 8px;
}
`)
})
})
test('negating a default value with a configured prefix', () => {
let config = {
prefix: 'tw-',
content: [{ raw: html`<div class="tw--mt"></div>` }],
theme: {
margin: {
DEFAULT: '15px',
},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css`
.tw--mt {
margin-top: -15px;
}
`)
})
})
test('arbitrary value keywords should be ignored', () => {
let config = {
content: [{ raw: html`<div class="-mt-[auto]"></div>` }],
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchCss(css``)
})
})
// This is a weird test but it used to crash because the negative prefix + variant used to cause an undefined utility to be generated
test('addUtilities without negative prefix + variant + negative prefix in content should not crash', async () => {
let config = {
content: [{ raw: html`<div class="hover:-top-lg"></div>` }],
plugins: [
({ addUtilities }) => {
addUtilities({
'.top-lg': {
top: '6rem',
},
})
},
],
}
let result = await run('@tailwind utilities', config)
expect(result.css).toMatchCss(css``)
})
+139 -159
View File
@@ -1,164 +1,144 @@
import { normalizeConfig } from '../src/util/normalizeConfig'
import resolveConfig from '../src/public/resolve-config'
import { crosscheck, run, css } from './util/run'
import { run, css } from './util/run'
crosscheck(({ stable, oxide }) => {
it.each`
config
${{ purge: [{ raw: 'text-center' }] }}
${{ purge: { content: [{ raw: 'text-center' }] } }}
${{ content: { content: [{ raw: 'text-center' }] } }}
`('should normalize content $config', ({ config }) => {
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
.text-center {
text-align: center;
}
`)
})
})
it.each`
config
${{ purge: { safelist: ['text-center'] } }}
${{ purge: { options: { safelist: ['text-center'] } } }}
${{ content: { safelist: ['text-center'] } }}
`('should normalize safelist $config', ({ config }) => {
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
.text-center {
text-align: center;
}
`)
})
})
oxide.test.todo('should normalize extractors')
stable.test.each`
config
${{ content: [{ raw: 'text-center' }], purge: { extract: () => ['font-bold'] } }}
${{ content: [{ raw: 'text-center' }], purge: { extract: { DEFAULT: () => ['font-bold'] } } }}
${{
content: [{ raw: 'text-center' }],
purge: { options: { defaultExtractor: () => ['font-bold'] } },
}}
${{
content: [{ raw: 'text-center' }],
purge: {
options: { extractors: [{ extractor: () => ['font-bold'], extensions: ['html'] }] },
},
}}
${{ content: [{ raw: 'text-center' }], purge: { extract: { html: () => ['font-bold'] } } }}
`('should normalize extractors $config', ({ config }) => {
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
.font-bold {
font-weight: 700;
}
`)
})
})
oxide.test.todo('should still be possible to use the "old" v2 config')
stable.test('should still be possible to use the "old" v2 config', () => {
let config = {
purge: {
content: [
{ raw: 'text-svelte', extension: 'svelte' },
{ raw: '# My Big Heading', extension: 'md' },
],
options: {
defaultExtractor(content) {
return content.split(' ').concat(['font-bold'])
},
},
extract: {
svelte(content) {
return content.replace('svelte', 'center').split(' ')
},
},
transform: {
md() {
return 'text-4xl'
},
},
},
theme: {
extends: {},
},
variants: {
extends: {},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
.text-center {
text-align: center;
}
.text-4xl {
font-size: 2.25rem;
line-height: 2.5rem;
}
.font-bold {
font-weight: 700;
}
`)
})
})
it('should keep content files with globs', () => {
let config = {
content: ['./example-folder/**/*.{html,js}'],
}
stable.expect(normalizeConfig(resolveConfig(config)).content).toEqual({
files: ['./example-folder/**/*.{html,js}'],
relative: false,
extract: {},
transform: {},
})
oxide.expect(normalizeConfig(resolveConfig(config)).content).toEqual({
files: ['./example-folder/**/*.{html,js}'],
relative: true,
extract: {},
transform: {},
})
})
it('should warn when we detect invalid globs with incorrect brace expansion', () => {
let config = {
content: [
'./{example-folder}/**/*.{html,js}',
'./{example-folder}/**/*.{html}',
'./example-folder/**/*.{html}',
],
}
let normalizedConfig = normalizeConfig(resolveConfig(config)).content
// No rewrite happens
stable.expect(normalizedConfig).toEqual({
files: [
'./{example-folder}/**/*.{html,js}',
'./{example-folder}/**/*.{html}',
'./example-folder/**/*.{html}',
],
relative: false,
extract: {},
transform: {},
})
oxide.expect(normalizedConfig).toEqual({
files: [
'./{example-folder}/**/*.{html,js}',
'./{example-folder}/**/*.{html}',
'./example-folder/**/*.{html}',
],
relative: true,
extract: {},
transform: {},
})
expect().toHaveBeenWarnedWith(['invalid-glob-braces'])
it.each`
config
${{ purge: [{ raw: 'text-center' }] }}
${{ purge: { content: [{ raw: 'text-center' }] } }}
${{ content: { content: [{ raw: 'text-center' }] } }}
`('should normalize content $config', ({ config }) => {
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
.text-center {
text-align: center;
}
`)
})
})
it.each`
config
${{ purge: { safelist: ['text-center'] } }}
${{ purge: { options: { safelist: ['text-center'] } } }}
${{ content: { safelist: ['text-center'] } }}
`('should normalize safelist $config', ({ config }) => {
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
.text-center {
text-align: center;
}
`)
})
})
test.each`
config
${{ content: [{ raw: 'text-center' }], purge: { extract: () => ['font-bold'] } }}
${{ content: [{ raw: 'text-center' }], purge: { extract: { DEFAULT: () => ['font-bold'] } } }}
${{
content: [{ raw: 'text-center' }],
purge: { options: { defaultExtractor: () => ['font-bold'] } },
}}
${{
content: [{ raw: 'text-center' }],
purge: {
options: { extractors: [{ extractor: () => ['font-bold'], extensions: ['html'] }] },
},
}}
${{ content: [{ raw: 'text-center' }], purge: { extract: { html: () => ['font-bold'] } } }}
`('should normalize extractors $config', ({ config }) => {
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
.font-bold {
font-weight: 700;
}
`)
})
})
test('should still be possible to use the "old" v2 config', () => {
let config = {
purge: {
content: [
{ raw: 'text-svelte', extension: 'svelte' },
{ raw: '# My Big Heading', extension: 'md' },
],
options: {
defaultExtractor(content) {
return content.split(' ').concat(['font-bold'])
},
},
extract: {
svelte(content) {
return content.replace('svelte', 'center').split(' ')
},
},
transform: {
md() {
return 'text-4xl'
},
},
},
theme: {
extends: {},
},
variants: {
extends: {},
},
}
return run('@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
.text-center {
text-align: center;
}
.text-4xl {
font-size: 2.25rem;
line-height: 2.5rem;
}
.font-bold {
font-weight: 700;
}
`)
})
})
it('should keep content files with globs', () => {
let config = {
content: ['./example-folder/**/*.{html,js}'],
}
expect(normalizeConfig(resolveConfig(config)).content).toEqual({
files: ['./example-folder/**/*.{html,js}'],
relative: false,
extract: {},
transform: {},
})
})
it('should warn when we detect invalid globs with incorrect brace expansion', () => {
let config = {
content: [
'./{example-folder}/**/*.{html,js}',
'./{example-folder}/**/*.{html}',
'./example-folder/**/*.{html}',
],
}
let normalizedConfig = normalizeConfig(resolveConfig(config)).content
// No rewrite happens
expect(normalizedConfig).toEqual({
files: [
'./{example-folder}/**/*.{html,js}',
'./{example-folder}/**/*.{html}',
'./example-folder/**/*.{html}',
],
relative: false,
extract: {},
transform: {},
})
expect().toHaveBeenWarnedWith(['invalid-glob-braces'])
})
+2 -5
View File
@@ -1,6 +1,5 @@
import { css, run } from './util/run'
import { normalize } from '../src/util/dataTypes'
import { crosscheck } from './util/run'
let table = [
['foo', 'foo'],
@@ -102,10 +101,8 @@ let table = [
['color(0_0_0_/_1.0)', 'color(0 0 0 / 1.0)'],
]
crosscheck(() => {
it.each(table)('normalize data: %s', (input, output) => {
expect(normalize(input)).toBe(output)
})
it.each(table)('normalize data: %s', (input, output) => {
expect(normalize(input)).toBe(output)
})
it('should not automatically inject the `var()` for properties that accept `<dashed-ident>` as the value', () => {
+59 -62
View File
@@ -1,66 +1,63 @@
import { normalizeScreens } from '../src/util/normalizeScreens'
import { crosscheck } from './util/run'
crosscheck(() => {
it('should normalize an array of string values', () => {
let screens = ['768px', '1200px']
it('should normalize an array of string values', () => {
let screens = ['768px', '1200px']
expect(normalizeScreens(screens)).toEqual([
{ name: '768px', not: false, values: [{ min: '768px', max: undefined }] },
{ name: '1200px', not: false, values: [{ min: '1200px', max: undefined }] },
])
})
it('should normalize an object with string values', () => {
let screens = {
a: '768px',
b: '1200px',
}
expect(normalizeScreens(screens)).toEqual([
{ name: 'a', not: false, values: [{ min: '768px', max: undefined }] },
{ name: 'b', not: false, values: [{ min: '1200px', max: undefined }] },
])
})
it('should normalize an object with object values', () => {
let screens = {
a: { min: '768px' },
b: { max: '1200px' },
}
expect(normalizeScreens(screens)).toEqual([
{ name: 'a', not: false, values: [{ min: '768px', max: undefined }] },
{ name: 'b', not: false, values: [{ min: undefined, max: '1200px' }] },
])
})
it('should normalize an object with multiple object values', () => {
let screens = {
a: [{ min: '768px' }, { max: '1200px' }],
}
expect(normalizeScreens(screens)).toEqual([
{
name: 'a',
not: false,
values: [
{ max: undefined, min: '768px', raw: undefined },
{ max: '1200px', min: undefined, raw: undefined },
],
},
])
})
it('should normalize an object with object values (min-width normalized to width)', () => {
let screens = {
a: { 'min-width': '768px' },
b: { max: '1200px' },
}
expect(normalizeScreens(screens)).toEqual([
{ name: 'a', not: false, values: [{ min: '768px', max: undefined }] },
{ name: 'b', not: false, values: [{ min: undefined, max: '1200px' }] },
])
})
expect(normalizeScreens(screens)).toEqual([
{ name: '768px', not: false, values: [{ min: '768px', max: undefined }] },
{ name: '1200px', not: false, values: [{ min: '1200px', max: undefined }] },
])
})
it('should normalize an object with string values', () => {
let screens = {
a: '768px',
b: '1200px',
}
expect(normalizeScreens(screens)).toEqual([
{ name: 'a', not: false, values: [{ min: '768px', max: undefined }] },
{ name: 'b', not: false, values: [{ min: '1200px', max: undefined }] },
])
})
it('should normalize an object with object values', () => {
let screens = {
a: { min: '768px' },
b: { max: '1200px' },
}
expect(normalizeScreens(screens)).toEqual([
{ name: 'a', not: false, values: [{ min: '768px', max: undefined }] },
{ name: 'b', not: false, values: [{ min: undefined, max: '1200px' }] },
])
})
it('should normalize an object with multiple object values', () => {
let screens = {
a: [{ min: '768px' }, { max: '1200px' }],
}
expect(normalizeScreens(screens)).toEqual([
{
name: 'a',
not: false,
values: [
{ max: undefined, min: '768px', raw: undefined },
{ max: '1200px', min: undefined, raw: undefined },
],
},
])
})
it('should normalize an object with object values (min-width normalized to width)', () => {
let screens = {
a: { 'min-width': '768px' },
b: { max: '1200px' },
}
expect(normalizeScreens(screens)).toEqual([
{ name: 'a', not: false, values: [{ min: '768px', max: undefined }] },
{ name: 'b', not: false, values: [{ min: undefined, max: '1200px' }] },
])
})
+1038 -1106
View File
File diff suppressed because it is too large Load Diff
+125 -127
View File
@@ -1,138 +1,136 @@
import { crosscheck, run, html, css } from './util/run'
import { run, html, css } from './util/run'
crosscheck(() => {
test('basic parallel variants', async () => {
let config = {
content: [
{
raw: html`<div
class="hover:test:font-black test:font-bold test:font-medium font-normal"
></div>`,
},
],
plugins: [
function test({ addVariant }) {
addVariant('test', ['& *::test', '&::test'])
},
],
}
test('basic parallel variants', async () => {
let config = {
content: [
{
raw: html`<div
class="hover:test:font-black test:font-bold test:font-medium font-normal"
></div>`,
},
],
plugins: [
function test({ addVariant }) {
addVariant('test', ['& *::test', '&::test'])
},
],
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-normal {
font-weight: 400;
}
.test\:font-bold ::test {
font-weight: 700;
}
.test\:font-medium ::test {
font-weight: 500;
}
.hover\:test\:font-black ::test:hover {
font-weight: 900;
}
.test\:font-bold::test {
font-weight: 700;
}
.test\:font-medium::test {
font-weight: 500;
}
.hover\:test\:font-black::test:hover {
font-weight: 900;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-normal {
font-weight: 400;
}
.test\:font-bold ::test {
font-weight: 700;
}
.test\:font-medium ::test {
font-weight: 500;
}
.hover\:test\:font-black ::test:hover {
font-weight: 900;
}
.test\:font-bold::test {
font-weight: 700;
}
.test\:font-medium::test {
font-weight: 500;
}
.hover\:test\:font-black::test:hover {
font-weight: 900;
}
`)
})
})
test('parallel variants can be generated using a function that returns parallel variants', async () => {
let config = {
content: [
{
raw: html`<div
class="hover:test:font-black test:font-bold test:font-medium font-normal"
></div>`,
},
],
plugins: [
function test({ addVariant }) {
addVariant('test', () => ['& *::test', '&::test'])
},
],
}
test('parallel variants can be generated using a function that returns parallel variants', async () => {
let config = {
content: [
{
raw: html`<div
class="hover:test:font-black test:font-bold test:font-medium font-normal"
></div>`,
},
],
plugins: [
function test({ addVariant }) {
addVariant('test', () => ['& *::test', '&::test'])
},
],
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-normal {
font-weight: 400;
}
.test\:font-bold ::test {
font-weight: 700;
}
.test\:font-medium ::test {
font-weight: 500;
}
.test\:font-bold::test {
font-weight: 700;
}
.test\:font-medium::test {
font-weight: 500;
}
.hover\:test\:font-black ::test:hover {
font-weight: 900;
}
.hover\:test\:font-black::test:hover {
font-weight: 900;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-normal {
font-weight: 400;
}
.test\:font-bold ::test {
font-weight: 700;
}
.test\:font-medium ::test {
font-weight: 500;
}
.test\:font-bold::test {
font-weight: 700;
}
.test\:font-medium::test {
font-weight: 500;
}
.hover\:test\:font-black ::test:hover {
font-weight: 900;
}
.hover\:test\:font-black::test:hover {
font-weight: 900;
}
`)
})
})
test('a function that returns parallel variants can modify the container', async () => {
let config = {
content: [
{
raw: html`<div
class="hover:test:font-black test:font-bold test:font-medium font-normal"
></div>`,
},
],
plugins: [
function test({ addVariant }) {
addVariant('test', ({ container }) => {
container.walkDecls((decl) => {
decl.value = `calc(0 + ${decl.value})`
})
test('a function that returns parallel variants can modify the container', async () => {
let config = {
content: [
{
raw: html`<div
class="hover:test:font-black test:font-bold test:font-medium font-normal"
></div>`,
},
],
plugins: [
function test({ addVariant }) {
addVariant('test', ({ container }) => {
container.walkDecls((decl) => {
decl.value = `calc(0 + ${decl.value})`
})
return ['& *::test', '&::test']
})
},
],
}
return ['& *::test', '&::test']
})
},
],
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-normal {
font-weight: 400;
}
.test\:font-bold ::test {
font-weight: 700;
}
.test\:font-medium ::test {
font-weight: 500;
}
.test\:font-bold::test {
font-weight: 700;
}
.test\:font-medium::test {
font-weight: 500;
}
.hover\:test\:font-black ::test:hover {
font-weight: 900;
}
.hover\:test\:font-black::test:hover {
font-weight: 900;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-normal {
font-weight: 400;
}
.test\:font-bold ::test {
font-weight: 700;
}
.test\:font-medium ::test {
font-weight: 500;
}
.test\:font-bold::test {
font-weight: 700;
}
.test\:font-medium::test {
font-weight: 500;
}
.hover\:test\:font-black ::test:hover {
font-weight: 900;
}
.hover\:test\:font-black::test:hover {
font-weight: 900;
}
`)
})
})
+192 -195
View File
@@ -1,203 +1,200 @@
import parseAnimationValue from '../src/util/parseAnimationValue'
import { crosscheck } from './util/run'
crosscheck(() => {
describe('Tailwind Defaults', () => {
it.each([
[
'spin 1s linear infinite',
{
value: 'spin 1s linear infinite',
name: 'spin',
duration: '1s',
timingFunction: 'linear',
iterationCount: 'infinite',
},
],
[
'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
{
value: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
name: 'ping',
duration: '1s',
timingFunction: 'cubic-bezier(0, 0, 0.2, 1)',
iterationCount: 'infinite',
},
],
[
'bounce 1s infinite',
{
value: 'bounce 1s infinite',
name: 'bounce',
duration: '1s',
iterationCount: 'infinite',
},
],
])('should be possible to parse: "%s"', (input, expected) => {
const parsed = parseAnimationValue(input)
expect(parsed).toHaveLength(1)
expect(parsed[0]).toEqual(expected)
})
})
describe('css variables', () => {
it('should be possible to use css variables', () => {
let parsed = parseAnimationValue('jump var(--animation-duration, 10s) linear infinite')
expect(parsed[0]).toEqual({
value: 'jump var(--animation-duration, 10s) linear infinite',
name: 'jump',
describe('Tailwind Defaults', () => {
it.each([
[
'spin 1s linear infinite',
{
value: 'spin 1s linear infinite',
name: 'spin',
duration: '1s',
timingFunction: 'linear',
iterationCount: 'infinite',
unknown: ['var(--animation-duration, 10s)'],
})
})
})
describe('MDN Examples', () => {
it.each([
[
'3s ease-in 1s 2 reverse both paused slidein',
{
value: '3s ease-in 1s 2 reverse both paused slidein',
delay: '1s',
direction: 'reverse',
duration: '3s',
fillMode: 'both',
iterationCount: '2',
name: 'slidein',
playState: 'paused',
timingFunction: 'ease-in',
},
],
[
'slidein 3s linear 1s',
{
value: 'slidein 3s linear 1s',
delay: '1s',
duration: '3s',
name: 'slidein',
timingFunction: 'linear',
},
],
['slidein 3s', { value: 'slidein 3s', duration: '3s', name: 'slidein' }],
])('should be possible to parse: "%s"', (input, expected) => {
const parsed = parseAnimationValue(input)
expect(parsed).toHaveLength(1)
expect(parsed[0]).toEqual(expected)
})
})
describe('duration & delay', () => {
it.each([
// Positive seconds (integer)
['spin 2s 1s linear', { duration: '2s', delay: '1s' }],
// Negative seconds (integer)
['spin -2s -1s linear', { duration: '-2s', delay: '-1s' }],
// Positive seconds (float)
['spin 2.321s 1.321s linear', { duration: '2.321s', delay: '1.321s' }],
// Negative seconds (float)
['spin -2.321s -1.321s linear', { duration: '-2.321s', delay: '-1.321s' }],
// Positive milliseconds (integer)
['spin 200ms 100ms linear', { duration: '200ms', delay: '100ms' }],
// Negative milliseconds (integer)
['spin -200ms -100ms linear', { duration: '-200ms', delay: '-100ms' }],
// Positive milliseconds (float)
['spin 200.321ms 100.321ms linear', { duration: '200.321ms', delay: '100.321ms' }],
// Negative milliseconds (float)
['spin -200.321ms -100.321ms linear', { duration: '-200.321ms', delay: '-100.321ms' }],
])('should be possible to parse "%s" into %o', (input, { duration, delay }) => {
const parsed = parseAnimationValue(input)
expect(parsed).toHaveLength(1)
expect(parsed[0].duration).toEqual(duration)
expect(parsed[0].delay).toEqual(delay)
})
})
describe('iteration count', () => {
it.each([
// Number
['1 spin 200s 100s linear', '1'],
['spin 2 200s 100s linear', '2'],
['spin 200s 3 100s linear', '3'],
['spin 200s 100s 4 linear', '4'],
['spin 200s 100s linear 5', '5'],
// Infinite
['infinite spin 200s 100s linear', 'infinite'],
['spin infinite 200s 100s linear', 'infinite'],
['spin 200s infinite 100s linear', 'infinite'],
['spin 200s 100s infinite linear', 'infinite'],
['spin 200s 100s linear infinite', 'infinite'],
])(
'should be possible to parse "%s" with an iteraction count of "%s"',
(input, iterationCount) => {
const parsed = parseAnimationValue(input)
expect(parsed).toHaveLength(1)
expect(parsed[0].iterationCount).toEqual(iterationCount)
}
)
})
describe('multiple animations', () => {
it('should be possible to parse multiple applications at once', () => {
const input = [
'spin 1s linear infinite',
'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
'pulse 2s cubic-bezier(0.4, 0, 0.6) infinite',
].join(',')
const parsed = parseAnimationValue(input)
expect(parsed).toHaveLength(3)
expect(parsed).toEqual([
{
value: 'spin 1s linear infinite',
name: 'spin',
duration: '1s',
timingFunction: 'linear',
iterationCount: 'infinite',
},
{
value: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
name: 'ping',
duration: '1s',
timingFunction: 'cubic-bezier(0, 0, 0.2, 1)',
iterationCount: 'infinite',
},
{
value: 'pulse 2s cubic-bezier(0.4, 0, 0.6) infinite',
name: 'pulse',
duration: '2s',
timingFunction: 'cubic-bezier(0.4, 0, 0.6)',
iterationCount: 'infinite',
},
])
})
})
it.each`
input | value | direction | playState | fillMode | iterationCount | timingFunction | duration | delay | name
${'1s spin 1s infinite'} | ${'1s spin 1s infinite'} | ${undefined} | ${undefined} | ${undefined} | ${'infinite'} | ${undefined} | ${'1s'} | ${'1s'} | ${'spin'}
${'infinite infinite 1s 1s'} | ${'infinite infinite 1s 1s'} | ${undefined} | ${undefined} | ${undefined} | ${'infinite'} | ${undefined} | ${'1s'} | ${'1s'} | ${'infinite'}
${'ease 1s ease 1s'} | ${'ease 1s ease 1s'} | ${undefined} | ${undefined} | ${undefined} | ${undefined} | ${'ease'} | ${'1s'} | ${'1s'} | ${'ease'}
${'normal paused backwards infinite ease-in 1s 2s name'} | ${'normal paused backwards infinite ease-in 1s 2s name'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
${'paused backwards infinite ease-in 1s 2s name normal'} | ${'paused backwards infinite ease-in 1s 2s name normal'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
${'backwards infinite ease-in 1s 2s name normal paused'} | ${'backwards infinite ease-in 1s 2s name normal paused'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
${'infinite ease-in 1s 2s name normal paused backwards'} | ${'infinite ease-in 1s 2s name normal paused backwards'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
${'ease-in 1s 2s name normal paused backwards infinite'} | ${'ease-in 1s 2s name normal paused backwards infinite'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
${'1s 2s name normal paused backwards infinite ease-in'} | ${'1s 2s name normal paused backwards infinite ease-in'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
${'2s name normal paused backwards infinite ease-in 1s'} | ${'2s name normal paused backwards infinite ease-in 1s'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'2s'} | ${'1s'} | ${'name'}
${'name normal paused backwards infinite ease-in 1s 2s'} | ${'name normal paused backwards infinite ease-in 1s 2s'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
${' name normal paused backwards infinite ease-in 1s 2s '} | ${'name normal paused backwards infinite ease-in 1s 2s'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
`('should parse "$input" correctly', ({ input, ...expected }) => {
let parsed = parseAnimationValue(input)
},
],
[
'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
{
value: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
name: 'ping',
duration: '1s',
timingFunction: 'cubic-bezier(0, 0, 0.2, 1)',
iterationCount: 'infinite',
},
],
[
'bounce 1s infinite',
{
value: 'bounce 1s infinite',
name: 'bounce',
duration: '1s',
iterationCount: 'infinite',
},
],
])('should be possible to parse: "%s"', (input, expected) => {
const parsed = parseAnimationValue(input)
expect(parsed).toHaveLength(1)
expect(parsed[0]).toEqual(expected)
})
})
describe('css variables', () => {
it('should be possible to use css variables', () => {
let parsed = parseAnimationValue('jump var(--animation-duration, 10s) linear infinite')
expect(parsed[0]).toEqual({
value: 'jump var(--animation-duration, 10s) linear infinite',
name: 'jump',
timingFunction: 'linear',
iterationCount: 'infinite',
unknown: ['var(--animation-duration, 10s)'],
})
})
})
describe('MDN Examples', () => {
it.each([
[
'3s ease-in 1s 2 reverse both paused slidein',
{
value: '3s ease-in 1s 2 reverse both paused slidein',
delay: '1s',
direction: 'reverse',
duration: '3s',
fillMode: 'both',
iterationCount: '2',
name: 'slidein',
playState: 'paused',
timingFunction: 'ease-in',
},
],
[
'slidein 3s linear 1s',
{
value: 'slidein 3s linear 1s',
delay: '1s',
duration: '3s',
name: 'slidein',
timingFunction: 'linear',
},
],
['slidein 3s', { value: 'slidein 3s', duration: '3s', name: 'slidein' }],
])('should be possible to parse: "%s"', (input, expected) => {
const parsed = parseAnimationValue(input)
expect(parsed).toHaveLength(1)
expect(parsed[0]).toEqual(expected)
})
})
describe('duration & delay', () => {
it.each([
// Positive seconds (integer)
['spin 2s 1s linear', { duration: '2s', delay: '1s' }],
// Negative seconds (integer)
['spin -2s -1s linear', { duration: '-2s', delay: '-1s' }],
// Positive seconds (float)
['spin 2.321s 1.321s linear', { duration: '2.321s', delay: '1.321s' }],
// Negative seconds (float)
['spin -2.321s -1.321s linear', { duration: '-2.321s', delay: '-1.321s' }],
// Positive milliseconds (integer)
['spin 200ms 100ms linear', { duration: '200ms', delay: '100ms' }],
// Negative milliseconds (integer)
['spin -200ms -100ms linear', { duration: '-200ms', delay: '-100ms' }],
// Positive milliseconds (float)
['spin 200.321ms 100.321ms linear', { duration: '200.321ms', delay: '100.321ms' }],
// Negative milliseconds (float)
['spin -200.321ms -100.321ms linear', { duration: '-200.321ms', delay: '-100.321ms' }],
])('should be possible to parse "%s" into %o', (input, { duration, delay }) => {
const parsed = parseAnimationValue(input)
expect(parsed).toHaveLength(1)
expect(parsed[0].duration).toEqual(duration)
expect(parsed[0].delay).toEqual(delay)
})
})
describe('iteration count', () => {
it.each([
// Number
['1 spin 200s 100s linear', '1'],
['spin 2 200s 100s linear', '2'],
['spin 200s 3 100s linear', '3'],
['spin 200s 100s 4 linear', '4'],
['spin 200s 100s linear 5', '5'],
// Infinite
['infinite spin 200s 100s linear', 'infinite'],
['spin infinite 200s 100s linear', 'infinite'],
['spin 200s infinite 100s linear', 'infinite'],
['spin 200s 100s infinite linear', 'infinite'],
['spin 200s 100s linear infinite', 'infinite'],
])(
'should be possible to parse "%s" with an iteraction count of "%s"',
(input, iterationCount) => {
const parsed = parseAnimationValue(input)
expect(parsed).toHaveLength(1)
expect(parsed[0].iterationCount).toEqual(iterationCount)
}
)
})
describe('multiple animations', () => {
it('should be possible to parse multiple applications at once', () => {
const input = [
'spin 1s linear infinite',
'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
'pulse 2s cubic-bezier(0.4, 0, 0.6) infinite',
].join(',')
const parsed = parseAnimationValue(input)
expect(parsed).toHaveLength(3)
expect(parsed).toEqual([
{
value: 'spin 1s linear infinite',
name: 'spin',
duration: '1s',
timingFunction: 'linear',
iterationCount: 'infinite',
},
{
value: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
name: 'ping',
duration: '1s',
timingFunction: 'cubic-bezier(0, 0, 0.2, 1)',
iterationCount: 'infinite',
},
{
value: 'pulse 2s cubic-bezier(0.4, 0, 0.6) infinite',
name: 'pulse',
duration: '2s',
timingFunction: 'cubic-bezier(0.4, 0, 0.6)',
iterationCount: 'infinite',
},
])
})
})
it.each`
input | value | direction | playState | fillMode | iterationCount | timingFunction | duration | delay | name
${'1s spin 1s infinite'} | ${'1s spin 1s infinite'} | ${undefined} | ${undefined} | ${undefined} | ${'infinite'} | ${undefined} | ${'1s'} | ${'1s'} | ${'spin'}
${'infinite infinite 1s 1s'} | ${'infinite infinite 1s 1s'} | ${undefined} | ${undefined} | ${undefined} | ${'infinite'} | ${undefined} | ${'1s'} | ${'1s'} | ${'infinite'}
${'ease 1s ease 1s'} | ${'ease 1s ease 1s'} | ${undefined} | ${undefined} | ${undefined} | ${undefined} | ${'ease'} | ${'1s'} | ${'1s'} | ${'ease'}
${'normal paused backwards infinite ease-in 1s 2s name'} | ${'normal paused backwards infinite ease-in 1s 2s name'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
${'paused backwards infinite ease-in 1s 2s name normal'} | ${'paused backwards infinite ease-in 1s 2s name normal'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
${'backwards infinite ease-in 1s 2s name normal paused'} | ${'backwards infinite ease-in 1s 2s name normal paused'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
${'infinite ease-in 1s 2s name normal paused backwards'} | ${'infinite ease-in 1s 2s name normal paused backwards'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
${'ease-in 1s 2s name normal paused backwards infinite'} | ${'ease-in 1s 2s name normal paused backwards infinite'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
${'1s 2s name normal paused backwards infinite ease-in'} | ${'1s 2s name normal paused backwards infinite ease-in'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
${'2s name normal paused backwards infinite ease-in 1s'} | ${'2s name normal paused backwards infinite ease-in 1s'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'2s'} | ${'1s'} | ${'name'}
${'name normal paused backwards infinite ease-in 1s 2s'} | ${'name normal paused backwards infinite ease-in 1s 2s'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
${' name normal paused backwards infinite ease-in 1s 2s '} | ${'name normal paused backwards infinite ease-in 1s 2s'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
`('should parse "$input" correctly', ({ input, ...expected }) => {
let parsed = parseAnimationValue(input)
expect(parsed).toHaveLength(1)
expect(parsed[0]).toEqual(expected)
})
+266 -268
View File
@@ -1,319 +1,317 @@
import parseObjectStyles from '../src/util/parseObjectStyles'
import postcss from 'postcss'
import { crosscheck, css } from './util/run'
import { css } from './util/run'
function toCss(nodes) {
return postcss.root({ nodes }).toString()
}
crosscheck(() => {
test('it parses simple single class definitions', () => {
const result = parseObjectStyles({
'.foobar': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
},
})
expect(toCss(result)).toMatchFormattedCss(css`
.foobar {
color: #fff;
background-color: red;
padding: 1rem;
}
`)
test('it parses simple single class definitions', () => {
const result = parseObjectStyles({
'.foobar': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
},
})
test('it parses multiple class definitions', () => {
const result = parseObjectStyles({
'.foo': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
},
'.bar': {
width: '200px',
height: '100px',
},
})
expect(toCss(result)).toMatchFormattedCss(css`
.foobar {
color: #fff;
background-color: red;
padding: 1rem;
}
`)
})
expect(toCss(result)).toMatchFormattedCss(css`
.foo {
color: #fff;
background-color: red;
padding: 1rem;
}
.bar {
width: 200px;
height: 100px;
}
`)
test('it parses multiple class definitions', () => {
const result = parseObjectStyles({
'.foo': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
},
'.bar': {
width: '200px',
height: '100px',
},
})
test('it parses nested pseudo-selectors', () => {
const result = parseObjectStyles({
'.foo': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
'&:hover': {
backgroundColor: 'orange',
},
'&:focus': {
backgroundColor: 'blue',
},
},
})
expect(toCss(result)).toMatchFormattedCss(css`
.foo {
color: #fff;
background-color: red;
padding: 1rem;
}
.bar {
width: 200px;
height: 100px;
}
`)
})
expect(toCss(result)).toMatchFormattedCss(css`
test('it parses nested pseudo-selectors', () => {
const result = parseObjectStyles({
'.foo': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
'&:hover': {
backgroundColor: 'orange',
},
'&:focus': {
backgroundColor: 'blue',
},
},
})
expect(toCss(result)).toMatchFormattedCss(css`
.foo {
color: #fff;
background-color: red;
padding: 1rem;
}
.foo:hover {
background-color: orange;
}
.foo:focus {
background-color: #00f;
}
`)
})
test('it parses top-level media queries', () => {
const result = parseObjectStyles({
'@media (min-width: 200px)': {
'.foo': {
backgroundColor: 'orange',
},
},
})
expect(toCss(result)).toMatchFormattedCss(css`
@media (min-width: 200px) {
.foo {
color: #fff;
background-color: red;
padding: 1rem;
}
.foo:hover {
background-color: orange;
}
.foo:focus {
background-color: #00f;
}
`)
})
}
`)
})
test('it parses top-level media queries', () => {
const result = parseObjectStyles({
test('it parses nested media queries', () => {
const result = parseObjectStyles({
'.foo': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
'@media (min-width: 200px)': {
'.foo': {
backgroundColor: 'orange',
},
backgroundColor: 'orange',
},
})
expect(toCss(result)).toMatchFormattedCss(css`
@media (min-width: 200px) {
.foo {
background-color: orange;
}
}
`)
},
})
test('it parses nested media queries', () => {
const result = parseObjectStyles({
'.foo': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
expect(toCss(result)).toMatchFormattedCss(css`
.foo {
color: #fff;
background-color: red;
padding: 1rem;
}
@media (min-width: 200px) {
.foo {
background-color: orange;
}
}
`)
})
test('it bubbles nested screen rules', () => {
const result = parseObjectStyles({
'.foo': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
'@screen sm': {
backgroundColor: 'orange',
},
},
})
expect(toCss(result)).toMatchFormattedCss(css`
.foo {
color: #fff;
background-color: red;
padding: 1rem;
}
@screen sm {
.foo {
background-color: orange;
}
}
`)
})
test('it parses pseudo-selectors in nested media queries', () => {
const result = parseObjectStyles({
'.foo': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
'&:hover': {
'@media (min-width: 200px)': {
backgroundColor: 'orange',
},
},
})
expect(toCss(result)).toMatchFormattedCss(css`
.foo {
color: #fff;
background-color: red;
padding: 1rem;
}
@media (min-width: 200px) {
.foo {
background-color: orange;
}
}
`)
},
})
test('it bubbles nested screen rules', () => {
const result = parseObjectStyles({
'.foo': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
'@screen sm': {
backgroundColor: 'orange',
},
},
})
expect(toCss(result)).toMatchFormattedCss(css`
.foo {
color: #fff;
background-color: red;
padding: 1rem;
}
@screen sm {
.foo {
background-color: orange;
}
}
`)
})
test('it parses pseudo-selectors in nested media queries', () => {
const result = parseObjectStyles({
'.foo': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
'&:hover': {
'@media (min-width: 200px)': {
backgroundColor: 'orange',
},
},
},
})
expect(toCss(result)).toMatchFormattedCss(css`
.foo {
color: #fff;
background-color: red;
padding: 1rem;
}
@media (min-width: 200px) {
.foo:hover {
background-color: orange;
}
}
`)
})
test('it parses descendant selectors', () => {
const result = parseObjectStyles({
'.foo': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
'.bar': {
backgroundColor: 'orange',
},
},
})
expect(toCss(result)).toMatchFormattedCss(css`
.foo {
color: #fff;
background-color: red;
padding: 1rem;
}
.foo .bar {
expect(toCss(result)).toMatchFormattedCss(css`
.foo {
color: #fff;
background-color: red;
padding: 1rem;
}
@media (min-width: 200px) {
.foo:hover {
background-color: orange;
}
`)
}
`)
})
test('it parses descendant selectors', () => {
const result = parseObjectStyles({
'.foo': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
'.bar': {
backgroundColor: 'orange',
},
},
})
test('it parses nested multi-class selectors', () => {
const result = parseObjectStyles({
'.foo': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
expect(toCss(result)).toMatchFormattedCss(css`
.foo {
color: #fff;
background-color: red;
padding: 1rem;
}
.foo .bar {
background-color: orange;
}
`)
})
test('it parses nested multi-class selectors', () => {
const result = parseObjectStyles({
'.foo': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
'&.bar': {
backgroundColor: 'orange',
},
},
})
expect(toCss(result)).toMatchFormattedCss(css`
.foo {
color: #fff;
background-color: red;
padding: 1rem;
}
.foo.bar {
background-color: orange;
}
`)
})
test('it parses nested multi-class selectors in media queries', () => {
const result = parseObjectStyles({
'.foo': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
'@media (min-width: 200px)': {
'&.bar': {
backgroundColor: 'orange',
},
},
})
},
})
expect(toCss(result)).toMatchFormattedCss(css`
.foo {
color: #fff;
background-color: red;
padding: 1rem;
}
expect(toCss(result)).toMatchFormattedCss(css`
.foo {
color: #fff;
background-color: red;
padding: 1rem;
}
@media (min-width: 200px) {
.foo.bar {
background-color: orange;
}
`)
}
`)
})
test('it strips empty selectors when nesting', () => {
const result = parseObjectStyles({
'.foo': {
'.bar': {
backgroundColor: 'orange',
},
},
})
test('it parses nested multi-class selectors in media queries', () => {
const result = parseObjectStyles({
expect(toCss(result)).toMatchFormattedCss(css`
.foo .bar {
background-color: orange;
}
`)
})
test('it can parse an array of styles', () => {
const result = parseObjectStyles([
{
'.foo': {
backgroundColor: 'orange',
},
},
{
'.bar': {
backgroundColor: 'red',
color: 'white',
padding: '1rem',
'@media (min-width: 200px)': {
'&.bar': {
backgroundColor: 'orange',
},
},
},
})
expect(toCss(result)).toMatchFormattedCss(css`
.foo {
color: #fff;
background-color: red;
padding: 1rem;
}
@media (min-width: 200px) {
.foo.bar {
background-color: orange;
}
}
`)
})
test('it strips empty selectors when nesting', () => {
const result = parseObjectStyles({
},
{
'.foo': {
'.bar': {
backgroundColor: 'orange',
},
backgroundColor: 'blue',
},
})
},
])
expect(toCss(result)).toMatchFormattedCss(css`
.foo .bar {
background-color: orange;
}
`)
})
expect(toCss(result)).toMatchFormattedCss(css`
.foo {
background-color: orange;
}
.bar {
background-color: red;
}
.foo {
background-color: #00f;
}
`)
})
test('it can parse an array of styles', () => {
const result = parseObjectStyles([
{
'.foo': {
backgroundColor: 'orange',
},
},
{
'.bar': {
backgroundColor: 'red',
},
},
{
'.foo': {
backgroundColor: 'blue',
},
},
])
expect(toCss(result)).toMatchFormattedCss(css`
.foo {
background-color: orange;
}
.bar {
background-color: red;
}
.foo {
background-color: #00f;
}
`)
test('custom properties preserve their case', () => {
const result = parseObjectStyles({
':root': {
'--colors-aColor-500': '0',
},
})
test('custom properties preserve their case', () => {
const result = parseObjectStyles({
':root': {
'--colors-aColor-500': '0',
},
})
expect(toCss(result)).toMatchFormattedCss(css`
:root {
--colors-aColor-500: 0;
}
`)
})
expect(toCss(result)).toMatchFormattedCss(css`
:root {
--colors-aColor-500: 0;
}
`)
})
+74 -85
View File
@@ -1,102 +1,91 @@
import { crosscheck, run, html, css, defaults } from '../util/run'
import { run, html, css, defaults } from '../util/run'
crosscheck(({ stable, oxide }) => {
it('should add the divide styles for divide-y and a default border color', () => {
let config = {
content: [{ raw: html`<div class="divide-y"></div>` }],
corePlugins: { preflight: false },
}
it('should add the divide styles for divide-y and a default border color', () => {
let config = {
content: [{ raw: html`<div class="divide-y"></div>` }],
corePlugins: { preflight: false },
}
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
expect(result.css).toMatchCss(css`
${defaults}
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
expect(result.css).toMatchCss(css`
${defaults}
.divide-y > :not([hidden]) ~ :not([hidden]) {
--tw-divide-y-reverse: 0;
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
}
`)
})
.divide-y > :not([hidden]) ~ :not([hidden]) {
--tw-divide-y-reverse: 0;
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
}
`)
})
})
it('should add the divide styles for divide-x and a default border color', () => {
let config = {
content: [{ raw: html`<div class="divide-x"></div>` }],
corePlugins: { preflight: false },
}
it('should add the divide styles for divide-x and a default border color', () => {
let config = {
content: [{ raw: html`<div class="divide-x"></div>` }],
corePlugins: { preflight: false },
}
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
stable.expect(result.css).toMatchCss(css`
${defaults}
.divide-x > :not([hidden]) ~ :not([hidden]) {
--tw-divide-x-reverse: 0;
border-right-width: calc(1px * var(--tw-divide-x-reverse));
border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
}
`)
oxide.expect(result.css).toMatchCss(css`
${defaults}
.divide-x > :not([hidden]) ~ :not([hidden]) {
--tw-divide-x-reverse: 0;
border-inline-end-width: calc(1px * var(--tw-divide-x-reverse));
border-inline-start-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
}
`)
})
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
expect(result.css).toMatchCss(css`
${defaults}
.divide-x > :not([hidden]) ~ :not([hidden]) {
--tw-divide-x-reverse: 0;
border-right-width: calc(1px * var(--tw-divide-x-reverse));
border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
}
`)
})
})
it('should add the divide styles for divide-y-reverse and a default border color', () => {
let config = {
content: [{ raw: html`<div class="divide-y-reverse"></div>` }],
corePlugins: { preflight: false },
}
it('should add the divide styles for divide-y-reverse and a default border color', () => {
let config = {
content: [{ raw: html`<div class="divide-y-reverse"></div>` }],
corePlugins: { preflight: false },
}
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
expect(result.css).toMatchCss(css`
${defaults}
.divide-y-reverse > :not([hidden]) ~ :not([hidden]) {
--tw-divide-y-reverse: 1;
}
`)
})
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
expect(result.css).toMatchCss(css`
${defaults}
.divide-y-reverse > :not([hidden]) ~ :not([hidden]) {
--tw-divide-y-reverse: 1;
}
`)
})
})
it('should add the divide styles for divide-x-reverse and a default border color', () => {
let config = {
content: [{ raw: html`<div class="divide-x-reverse"></div>` }],
corePlugins: { preflight: false },
}
it('should add the divide styles for divide-x-reverse and a default border color', () => {
let config = {
content: [{ raw: html`<div class="divide-x-reverse"></div>` }],
corePlugins: { preflight: false },
}
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
expect(result.css).toMatchCss(css`
${defaults}
.divide-x-reverse > :not([hidden]) ~ :not([hidden]) {
--tw-divide-x-reverse: 1;
}
`)
})
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
expect(result.css).toMatchCss(css`
${defaults}
.divide-x-reverse > :not([hidden]) ~ :not([hidden]) {
--tw-divide-x-reverse: 1;
}
`)
})
})
it('should only inject the base styles once if we use divide and border at the same time', () => {
let config = {
content: [{ raw: html`<div class="divide-y border-r"></div>` }],
corePlugins: { preflight: false },
}
it('should only inject the base styles once if we use divide and border at the same time', () => {
let config = {
content: [{ raw: html`<div class="divide-y border-r"></div>` }],
corePlugins: { preflight: false },
}
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
expect(result.css).toMatchCss(css`
${defaults}
.divide-y > :not([hidden]) ~ :not([hidden]) {
--tw-divide-y-reverse: 0;
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
}
.border-r {
border-right-width: 1px;
}
`)
})
return run('@tailwind base; @tailwind utilities;', config).then((result) => {
expect(result.css).toMatchCss(css`
${defaults}
.divide-y > :not([hidden]) ~ :not([hidden]) {
--tw-divide-y-reverse: 0;
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
}
.border-r {
border-right-width: 1px;
}
`)
})
})
+77 -79
View File
@@ -1,101 +1,99 @@
import { crosscheck, run, html, css } from '../util/run'
import { run, html, css } from '../util/run'
crosscheck(() => {
test('font-family utilities can be defined as a string', () => {
let config = {
content: [{ raw: html`<div class="font-sans"></div>` }],
theme: {
fontFamily: {
sans: 'Helvetica, Arial, sans-serif',
},
test('font-family utilities can be defined as a string', () => {
let config = {
content: [{ raw: html`<div class="font-sans"></div>` }],
theme: {
fontFamily: {
sans: 'Helvetica, Arial, sans-serif',
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-sans {
font-family: Helvetica, Arial, sans-serif;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-sans {
font-family: Helvetica, Arial, sans-serif;
}
`)
})
})
test('font-family utilities can be defined as an array', () => {
let config = {
content: [{ raw: html`<div class="font-sans"></div>` }],
theme: {
fontFamily: {
sans: ['Helvetica', 'Arial', 'sans-serif'],
},
test('font-family utilities can be defined as an array', () => {
let config = {
content: [{ raw: html`<div class="font-sans"></div>` }],
theme: {
fontFamily: {
sans: ['Helvetica', 'Arial', 'sans-serif'],
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-sans {
font-family: Helvetica, Arial, sans-serif;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-sans {
font-family: Helvetica, Arial, sans-serif;
}
`)
})
})
test('font-family values are not automatically escaped', () => {
let config = {
content: [{ raw: html`<div class="font-sans"></div>` }],
theme: {
fontFamily: {
sans: ["'Exo 2'", 'sans-serif'],
},
test('font-family values are not automatically escaped', () => {
let config = {
content: [{ raw: html`<div class="font-sans"></div>` }],
theme: {
fontFamily: {
sans: ["'Exo 2'", 'sans-serif'],
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-sans {
font-family: 'Exo 2', sans-serif;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-sans {
font-family: 'Exo 2', sans-serif;
}
`)
})
})
test('font-feature-settings can be provided when families are defined as a string', () => {
let config = {
content: [{ raw: html`<div class="font-sans"></div>` }],
theme: {
fontFamily: {
sans: ['Helvetica, Arial, sans-serif', { fontFeatureSettings: '"cv11", "ss01"' }],
},
test('font-feature-settings can be provided when families are defined as a string', () => {
let config = {
content: [{ raw: html`<div class="font-sans"></div>` }],
theme: {
fontFamily: {
sans: ['Helvetica, Arial, sans-serif', { fontFeatureSettings: '"cv11", "ss01"' }],
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-sans {
font-feature-settings: 'cv11', 'ss01';
font-family: Helvetica, Arial, sans-serif;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-sans {
font-feature-settings: 'cv11', 'ss01';
font-family: Helvetica, Arial, sans-serif;
}
`)
})
})
test('font-feature-settings can be provided when families are defined as an array', () => {
let config = {
content: [{ raw: html`<div class="font-sans"></div>` }],
theme: {
fontFamily: {
sans: [['Helvetica', 'Arial', 'sans-serif'], { fontFeatureSettings: '"cv11", "ss01"' }],
},
test('font-feature-settings can be provided when families are defined as an array', () => {
let config = {
content: [{ raw: html`<div class="font-sans"></div>` }],
theme: {
fontFamily: {
sans: [['Helvetica', 'Arial', 'sans-serif'], { fontFeatureSettings: '"cv11", "ss01"' }],
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-sans {
font-feature-settings: 'cv11', 'ss01';
font-family: Helvetica, Arial, sans-serif;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-sans {
font-feature-settings: 'cv11', 'ss01';
font-family: Helvetica, Arial, sans-serif;
}
`)
})
})
+170 -172
View File
@@ -1,197 +1,195 @@
import { crosscheck, run, html, css } from '../util/run'
import { run, html, css } from '../util/run'
crosscheck(() => {
test('font-size utilities can include a default line-height', () => {
let config = {
content: [{ raw: html`<div class="text-md text-sm text-lg"></div>` }],
theme: {
fontSize: {
sm: '12px',
md: ['16px', '24px'],
lg: ['20px', '28px'],
},
test('font-size utilities can include a default line-height', () => {
let config = {
content: [{ raw: html`<div class="text-md text-sm text-lg"></div>` }],
theme: {
fontSize: {
sm: '12px',
md: ['16px', '24px'],
lg: ['20px', '28px'],
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchCss(css`
.text-lg {
font-size: 20px;
line-height: 28px;
}
.text-md {
font-size: 16px;
line-height: 24px;
}
.text-sm {
font-size: 12px;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchCss(css`
.text-lg {
font-size: 20px;
line-height: 28px;
}
.text-md {
font-size: 16px;
line-height: 24px;
}
.text-sm {
font-size: 12px;
}
`)
})
})
test('font-size utilities can include a default letter-spacing', () => {
let config = {
content: [{ raw: html`<div class="text-md text-sm text-lg"></div>` }],
theme: {
fontSize: {
sm: '12px',
md: ['16px', { letterSpacing: '-0.01em' }],
lg: ['20px', { letterSpacing: '-0.02em' }],
},
test('font-size utilities can include a default letter-spacing', () => {
let config = {
content: [{ raw: html`<div class="text-md text-sm text-lg"></div>` }],
theme: {
fontSize: {
sm: '12px',
md: ['16px', { letterSpacing: '-0.01em' }],
lg: ['20px', { letterSpacing: '-0.02em' }],
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchCss(css`
.text-lg {
letter-spacing: -0.02em;
font-size: 20px;
}
.text-md {
letter-spacing: -0.01em;
font-size: 16px;
}
.text-sm {
font-size: 12px;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchCss(css`
.text-lg {
letter-spacing: -0.02em;
font-size: 20px;
}
.text-md {
letter-spacing: -0.01em;
font-size: 16px;
}
.text-sm {
font-size: 12px;
}
`)
})
})
test('font-size utilities can include a default line-height and letter-spacing', () => {
let config = {
content: [{ raw: html`<div class="text-md text-sm text-lg"></div>` }],
theme: {
fontSize: {
sm: '12px',
md: ['16px', { lineHeight: '24px', letterSpacing: '-0.01em' }],
lg: ['20px', { lineHeight: '28px', letterSpacing: '-0.02em' }],
},
test('font-size utilities can include a default line-height and letter-spacing', () => {
let config = {
content: [{ raw: html`<div class="text-md text-sm text-lg"></div>` }],
theme: {
fontSize: {
sm: '12px',
md: ['16px', { lineHeight: '24px', letterSpacing: '-0.01em' }],
lg: ['20px', { lineHeight: '28px', letterSpacing: '-0.02em' }],
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchCss(css`
.text-lg {
letter-spacing: -0.02em;
font-size: 20px;
line-height: 28px;
}
.text-md {
letter-spacing: -0.01em;
font-size: 16px;
line-height: 24px;
}
.text-sm {
font-size: 12px;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchCss(css`
.text-lg {
letter-spacing: -0.02em;
font-size: 20px;
line-height: 28px;
}
.text-md {
letter-spacing: -0.01em;
font-size: 16px;
line-height: 24px;
}
.text-sm {
font-size: 12px;
}
`)
})
})
test('font-size utilities can include a font-weight', () => {
let config = {
content: [{ raw: html`<div class="text-md text-sm text-lg"></div>` }],
theme: {
fontSize: {
sm: '12px',
md: ['16px', { lineHeight: '24px', fontWeight: 500 }],
lg: ['20px', { lineHeight: '28px', fontWeight: 'bold' }],
},
test('font-size utilities can include a font-weight', () => {
let config = {
content: [{ raw: html`<div class="text-md text-sm text-lg"></div>` }],
theme: {
fontSize: {
sm: '12px',
md: ['16px', { lineHeight: '24px', fontWeight: 500 }],
lg: ['20px', { lineHeight: '28px', fontWeight: 'bold' }],
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchCss(css`
.text-lg {
font-size: 20px;
font-weight: bold;
line-height: 28px;
}
.text-md {
font-size: 16px;
font-weight: 500;
line-height: 24px;
}
.text-sm {
font-size: 12px;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchCss(css`
.text-lg {
font-size: 20px;
font-weight: bold;
line-height: 28px;
}
.text-md {
font-size: 16px;
font-weight: 500;
line-height: 24px;
}
.text-sm {
font-size: 12px;
}
`)
})
})
test('font-size utilities can include a line-height modifier', () => {
let config = {
content: [
{
raw: html`<div class="text-sm md:text-base">
<div class="text-sm/6 md:text-base/7"></div>
<div class="text-sm/[21px] md:text-base/[33px]"></div>
<div class="text-[13px]/6 md:text-[19px]/8"></div>
<div class="text-[17px]/[23px] md:text-[21px]/[29px]"></div>
<div class="text-sm/999 md:text-base/000"></div>
</div>`,
},
],
theme: {
fontSize: {
sm: ['12px', '20px'],
base: ['16px', '24px'],
},
lineHeight: {
6: '24px',
7: '28px',
8: '32px',
},
test('font-size utilities can include a line-height modifier', () => {
let config = {
content: [
{
raw: html`<div class="text-sm md:text-base">
<div class="text-sm/6 md:text-base/7"></div>
<div class="text-sm/[21px] md:text-base/[33px]"></div>
<div class="text-[13px]/6 md:text-[19px]/8"></div>
<div class="text-[17px]/[23px] md:text-[21px]/[29px]"></div>
<div class="text-sm/999 md:text-base/000"></div>
</div>`,
},
],
theme: {
fontSize: {
sm: ['12px', '20px'],
base: ['16px', '24px'],
},
lineHeight: {
6: '24px',
7: '28px',
8: '32px',
},
}
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchCss(css`
.text-\[13px\]\/6 {
font-size: 13px;
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchCss(css`
.text-\[13px\]\/6 {
font-size: 13px;
line-height: 24px;
}
.text-\[17px\]\/\[23px\] {
font-size: 17px;
line-height: 23px;
}
.text-sm {
font-size: 12px;
line-height: 20px;
}
.text-sm\/6 {
font-size: 12px;
line-height: 24px;
}
.text-sm\/\[21px\] {
font-size: 12px;
line-height: 21px;
}
@media (min-width: 768px) {
.md\:text-\[19px\]\/8 {
font-size: 19px;
line-height: 32px;
}
.md\:text-\[21px\]\/\[29px\] {
font-size: 21px;
line-height: 29px;
}
.md\:text-base {
font-size: 16px;
line-height: 24px;
}
.text-\[17px\]\/\[23px\] {
font-size: 17px;
line-height: 23px;
.md\:text-base\/7 {
font-size: 16px;
line-height: 28px;
}
.text-sm {
font-size: 12px;
line-height: 20px;
}
.text-sm\/6 {
font-size: 12px;
line-height: 24px;
}
.text-sm\/\[21px\] {
font-size: 12px;
line-height: 21px;
}
@media (min-width: 768px) {
.md\:text-\[19px\]\/8 {
font-size: 19px;
line-height: 32px;
}
.md\:text-\[21px\]\/\[29px\] {
font-size: 21px;
line-height: 29px;
}
.md\:text-base {
font-size: 16px;
line-height: 24px;
}
.md\:text-base\/7 {
font-size: 16px;
line-height: 28px;
}
.md\:text-base\/\[33px\] {
font-size: 16px;
line-height: 33px;
}
.md\:text-base\/\[33px\] {
font-size: 16px;
line-height: 33px;
}
`)
})
}
`)
})
})
+147 -183
View File
@@ -1,191 +1,155 @@
import { crosscheck, run, html, css } from '../util/run'
import { run, html, css } from '../util/run'
crosscheck(({ stable, oxide }) => {
test('opacity variables are given to colors defined as closures', () => {
let config = {
content: [
{
raw: html`<div
class="text-primary text-secondary from-primary from-secondary via-primary via-secondary to-primary to-secondary text-opacity-50"
></div>`,
},
],
theme: {
colors: {
primary: ({ opacityVariable, opacityValue }) => {
if (opacityValue !== undefined) {
return `rgba(31,31,31,${opacityValue})`
}
if (opacityVariable !== undefined) {
return `rgba(31,31,31,var(${opacityVariable},1))`
}
return `rgb(31,31,31)`
},
secondary: 'hsl(10, 50%, 50%)',
},
opacity: {
50: '0.5',
},
test('opacity variables are given to colors defined as closures', () => {
let config = {
content: [
{
raw: html`<div
class="text-primary text-secondary from-primary from-secondary via-primary via-secondary to-primary to-secondary text-opacity-50"
></div>`,
},
}
],
theme: {
colors: {
primary: ({ opacityVariable, opacityValue }) => {
if (opacityValue !== undefined) {
return `rgba(31,31,31,${opacityValue})`
}
return run('@tailwind utilities', config).then((result) => {
stable.expect(result.css).toMatchFormattedCss(css`
.from-primary {
--tw-gradient-from: #1f1f1f var(--tw-gradient-from-position);
--tw-gradient-to: #1f1f1f00 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.from-secondary {
--tw-gradient-from: #bf5540 var(--tw-gradient-from-position);
--tw-gradient-to: #bf554000 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.via-primary {
--tw-gradient-to: #1f1f1f00 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), #1f1f1f var(--tw-gradient-via-position),
var(--tw-gradient-to);
}
.via-secondary {
--tw-gradient-to: #bf554000 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), #bf5540 var(--tw-gradient-via-position),
var(--tw-gradient-to);
}
.to-primary {
--tw-gradient-to: #1f1f1f var(--tw-gradient-to-position);
}
.to-secondary {
--tw-gradient-to: #bf5540 var(--tw-gradient-to-position);
}
.text-primary {
--tw-text-opacity: 1;
color: rgba(31, 31, 31, var(--tw-text-opacity));
}
.text-secondary {
--tw-text-opacity: 1;
color: hsl(10 50% 50% / var(--tw-text-opacity));
}
.text-opacity-50 {
--tw-text-opacity: 0.5;
}
`)
oxide.expect(result.css).toMatchFormattedCss(css`
.from-primary {
--tw-gradient-from: #1f1f1f var(--tw-gradient-from-position);
--tw-gradient-to: #1f1f1f00 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.from-secondary {
--tw-gradient-from: #bf5540 var(--tw-gradient-from-position);
--tw-gradient-to: #bf554000 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.via-primary {
--tw-gradient-to: #1f1f1f00 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), #1f1f1f var(--tw-gradient-via-position),
var(--tw-gradient-to);
}
.via-secondary {
--tw-gradient-to: #bf554000 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), #bf5540 var(--tw-gradient-via-position),
var(--tw-gradient-to);
}
.to-primary {
--tw-gradient-to: #1f1f1f var(--tw-gradient-to-position);
}
.to-secondary {
--tw-gradient-to: #bf5540 var(--tw-gradient-to-position);
}
.text-primary {
color: #1f1f1f;
}
.text-secondary {
color: #bf5540;
}
`)
})
})
if (opacityVariable !== undefined) {
return `rgba(31,31,31,var(${opacityVariable},1))`
}
test('gradient color stop position', () => {
let config = {
content: [
{
raw: html`
<div>
<div
class="bg-gradient-to-r from-red-500 from-50 via-pink-500 via-75 to-violet-400 to-80"
></div>
<div class="from-10% from-[11%] from-[12px] from-[--from-value]"></div>
<div class="via-20% via-[12%] via-[123px] via-[--via-value]"></div>
<div class="to-30% to-[13%] to-[14px] to-[--to-value]"></div>
</div>
`,
return `rgb(31,31,31)`
},
],
theme: {},
}
secondary: 'hsl(10, 50%, 50%)',
},
opacity: {
50: '0.5',
},
},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.bg-gradient-to-r {
background-image: linear-gradient(to right, var(--tw-gradient-stops));
}
.from-\[--from-value\] {
--tw-gradient-from: var(--from-value) var(--tw-gradient-from-position);
--tw-gradient-to: #fff0 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.from-red-500 {
--tw-gradient-from: #ef4444 var(--tw-gradient-from-position);
--tw-gradient-to: #ef444400 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.from-10\% {
--tw-gradient-from-position: 10%;
}
.from-\[11\%\] {
--tw-gradient-from-position: 11%;
}
.from-\[12px\] {
--tw-gradient-from-position: 12px;
}
.via-\[--via-value\] {
--tw-gradient-to: #fff0 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from),
var(--via-value) var(--tw-gradient-via-position), var(--tw-gradient-to);
}
.via-pink-500 {
--tw-gradient-to: #ec489900 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), #ec4899 var(--tw-gradient-via-position),
var(--tw-gradient-to);
}
.via-20\% {
--tw-gradient-via-position: 20%;
}
.via-\[12\%\] {
--tw-gradient-via-position: 12%;
}
.via-\[123px\] {
--tw-gradient-via-position: 123px;
}
.to-\[--to-value\] {
--tw-gradient-to: var(--to-value) var(--tw-gradient-to-position);
}
.to-violet-400 {
--tw-gradient-to: #a78bfa var(--tw-gradient-to-position);
}
.to-30\% {
--tw-gradient-to-position: 30%;
}
.to-\[13\%\] {
--tw-gradient-to-position: 13%;
}
.to-\[14px\] {
--tw-gradient-to-position: 14px;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.from-primary {
--tw-gradient-from: #1f1f1f var(--tw-gradient-from-position);
--tw-gradient-to: #1f1f1f00 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.from-secondary {
--tw-gradient-from: #bf5540 var(--tw-gradient-from-position);
--tw-gradient-to: #bf554000 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.via-primary {
--tw-gradient-to: #1f1f1f00 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), #1f1f1f var(--tw-gradient-via-position),
var(--tw-gradient-to);
}
.via-secondary {
--tw-gradient-to: #bf554000 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), #bf5540 var(--tw-gradient-via-position),
var(--tw-gradient-to);
}
.to-primary {
--tw-gradient-to: #1f1f1f var(--tw-gradient-to-position);
}
.to-secondary {
--tw-gradient-to: #bf5540 var(--tw-gradient-to-position);
}
.text-primary {
--tw-text-opacity: 1;
color: rgba(31, 31, 31, var(--tw-text-opacity));
}
.text-secondary {
--tw-text-opacity: 1;
color: hsl(10 50% 50% / var(--tw-text-opacity));
}
.text-opacity-50 {
--tw-text-opacity: 0.5;
}
`)
})
})
test('gradient color stop position', () => {
let config = {
content: [
{
raw: html`
<div>
<div
class="bg-gradient-to-r from-red-500 from-50 via-pink-500 via-75 to-violet-400 to-80"
></div>
<div class="from-10% from-[11%] from-[12px] from-[--from-value]"></div>
<div class="via-20% via-[12%] via-[123px] via-[--via-value]"></div>
<div class="to-30% to-[13%] to-[14px] to-[--to-value]"></div>
</div>
`,
},
],
theme: {},
}
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.bg-gradient-to-r {
background-image: linear-gradient(to right, var(--tw-gradient-stops));
}
.from-\[--from-value\] {
--tw-gradient-from: var(--from-value) var(--tw-gradient-from-position);
--tw-gradient-to: #fff0 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.from-red-500 {
--tw-gradient-from: #ef4444 var(--tw-gradient-from-position);
--tw-gradient-to: #ef444400 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.from-10\% {
--tw-gradient-from-position: 10%;
}
.from-\[11\%\] {
--tw-gradient-from-position: 11%;
}
.from-\[12px\] {
--tw-gradient-from-position: 12px;
}
.via-\[--via-value\] {
--tw-gradient-to: #fff0 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from),
var(--via-value) var(--tw-gradient-via-position), var(--tw-gradient-to);
}
.via-pink-500 {
--tw-gradient-to: #ec489900 var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), #ec4899 var(--tw-gradient-via-position),
var(--tw-gradient-to);
}
.via-20\% {
--tw-gradient-via-position: 20%;
}
.via-\[12\%\] {
--tw-gradient-via-position: 12%;
}
.via-\[123px\] {
--tw-gradient-via-position: 123px;
}
.to-\[--to-value\] {
--tw-gradient-to: var(--to-value) var(--tw-gradient-to-position);
}
.to-violet-400 {
--tw-gradient-to: #a78bfa var(--tw-gradient-to-position);
}
.to-30\% {
--tw-gradient-to-position: 30%;
}
.to-\[13\%\] {
--tw-gradient-to-position: 13%;
}
.to-\[14px\] {
--tw-gradient-to-position: 14px;
}
`)
})
})
+63 -94
View File
@@ -1,102 +1,71 @@
import { crosscheck, run, html, css, defaults } from './util/run'
import { run, html, css, defaults } from './util/run'
crosscheck(({ stable, oxide }) => {
it('should be possible to use contrast-more and contrast-less variants', () => {
let config = {
content: [
{
raw: html`<div class="bg-white contrast-more:bg-pink-500 contrast-less:bg-black"></div>`,
},
],
corePlugins: { preflight: false },
}
it('should be possible to use contrast-more and contrast-less variants', () => {
let config = {
content: [
{
raw: html`<div class="bg-white contrast-more:bg-pink-500 contrast-less:bg-black"></div>`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
stable.expect(result.css).toMatchFormattedCss(css`
${defaults}
.bg-white {
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
.bg-white {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
@media (prefers-contrast: more) {
.contrast-more\:bg-pink-500 {
--tw-bg-opacity: 1;
background-color: rgb(236 72 153 / var(--tw-bg-opacity));
}
}
@media (prefers-contrast: less) {
.contrast-less\:bg-black {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
}
`)
})
})
it('dark mode should appear after the contrast variants', () => {
let config = {
content: [{ raw: html`<div class="contrast-more:bg-black dark:bg-white"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
${defaults}
@media (prefers-contrast: more) {
.contrast-more\:bg-black {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
}
@media (prefers-color-scheme: dark) {
.dark\:bg-white {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
@media (prefers-contrast: more) {
.contrast-more\:bg-pink-500 {
--tw-bg-opacity: 1;
background-color: rgb(236 72 153 / var(--tw-bg-opacity));
}
}
@media (prefers-contrast: less) {
.contrast-less\:bg-black {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
}
`)
oxide.expect(result.css).toMatchFormattedCss(css`
${defaults}
.bg-white {
background-color: #fff;
}
@media (prefers-contrast: more) {
.contrast-more\:bg-pink-500 {
background-color: #ec4899;
}
}
@media (prefers-contrast: less) {
.contrast-less\:bg-black {
background-color: #000;
}
}
`)
})
})
it('dark mode should appear after the contrast variants', () => {
let config = {
content: [{ raw: html`<div class="contrast-more:bg-black dark:bg-white"></div>` }],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
stable.expect(result.css).toMatchFormattedCss(css`
${defaults}
@media (prefers-contrast: more) {
.contrast-more\:bg-black {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
}
@media (prefers-color-scheme: dark) {
.dark\:bg-white {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
}
`)
oxide.expect(result.css).toMatchFormattedCss(css`
${defaults}
@media (prefers-contrast: more) {
.contrast-more\:bg-black {
background-color: #000;
}
}
@media (prefers-color-scheme: dark) {
.dark\:bg-white {
background-color: #fff;
}
}
`)
})
}
`)
})
})
+575 -591
View File
File diff suppressed because it is too large Load Diff
+12 -15
View File
@@ -1,20 +1,17 @@
import prefix from '../src/util/prefixSelector'
import { crosscheck } from './util/run'
crosscheck(() => {
test('it prefixes classes with the provided prefix', () => {
expect(prefix('tw-', '.foo')).toEqual('.tw-foo')
})
test('it prefixes classes with the provided prefix', () => {
expect(prefix('tw-', '.foo')).toEqual('.tw-foo')
})
test('it properly prefixes selectors with non-standard characters', () => {
expect(prefix('tw-', '.hello\\:world')).toEqual('.tw-hello\\:world')
expect(prefix('tw-', '.foo\\/bar')).toEqual('.tw-foo\\/bar')
expect(prefix('tw-', '.wew\\.lad')).toEqual('.tw-wew\\.lad')
})
test('it properly prefixes selectors with non-standard characters', () => {
expect(prefix('tw-', '.hello\\:world')).toEqual('.tw-hello\\:world')
expect(prefix('tw-', '.foo\\/bar')).toEqual('.tw-foo\\/bar')
expect(prefix('tw-', '.wew\\.lad')).toEqual('.tw-wew\\.lad')
})
test('it prefixes all classes in a selector', () => {
expect(prefix('tw-', '.btn-blue .w-1\\/4 > h1.text-xl + a .bar')).toEqual(
'.tw-btn-blue .tw-w-1\\/4 > h1.tw-text-xl + a .tw-bar'
)
})
test('it prefixes all classes in a selector', () => {
expect(prefix('tw-', '.btn-blue .w-1\\/4 > h1.text-xl + a .bar')).toEqual(
'.tw-btn-blue .tw-w-1\\/4 > h1.tw-text-xl + a .tw-bar'
)
})
+16 -20
View File
@@ -1,25 +1,21 @@
import { crosscheck, run, html, css } from './util/run'
import { run, html, css } from './util/run'
crosscheck(({ stable, oxide }) => {
it('preflight has a correct border color fallback', () => {
let config = {
content: [{ raw: html`<div class="border-black"></div>` }],
theme: {
borderColor: ({ theme }) => theme('colors'),
},
plugins: [],
corePlugins: { preflight: true },
}
it('preflight has a correct border color fallback', () => {
let config = {
content: [{ raw: html`<div class="border-black"></div>` }],
theme: {
borderColor: ({ theme }) => theme('colors'),
},
plugins: [],
corePlugins: { preflight: true },
}
let input = css`
@tailwind base;
@tailwind utilities;
`
let input = css`
@tailwind base;
@tailwind utilities;
`
return run(input, config).then((result) => {
stable.expect(result.css).toContain(`border-color: currentColor;`)
// Lightning CSS optimizes this to just `border-color: 0 solid;` based on the br value.
oxide.expect(result.css).toContain(`border: 0 solid;`)
})
return run(input, config).then((result) => {
expect(result.css).toContain(`border-color: currentColor;`)
})
})
+157 -161
View File
@@ -1,168 +1,164 @@
import fs from 'fs'
import path from 'path'
import { crosscheck, run, html, css } from './util/run'
import { run, html, css } from './util/run'
crosscheck(({ stable }) => {
it('raw content', () => {
let config = {
content: [
{
raw: html`
<div class="sr-only"></div>
<div class="content-center"></div>
<div class="items-start"></div>
<div class="self-end"></div>
<div class="animate-none"></div>
<div class="animate-spin"></div>
<div class="appearance-none"></div>
<div class="bg-local"></div>
<div class="bg-clip-border"></div>
<div class="bg-green-500"></div>
<div class="bg-gradient-to-r"></div>
<div class="bg-opacity-20"></div>
<div class="bg-top"></div>
<div class="bg-no-repeat"></div>
<div class="bg-cover"></div>
<div class="bg-origin-border bg-origin-padding bg-origin-content"></div>
<div class="border-collapse"></div>
<div class="border-black"></div>
<div class="border-opacity-10"></div>
<div class="rounded-md"></div>
<div class="border-solid"></div>
<div class="border"></div>
<div class="border-2"></div>
<div class="shadow"></div>
<div class="shadow-md"></div>
<div class="shadow-lg"></div>
<div class="decoration-clone decoration-slice"></div>
<div class="box-border"></div>
<div class="clear-left"></div>
<div class="container"></div>
<div class="cursor-pointer"></div>
<div class="hidden inline-grid"></div>
<div class="divide-gray-200"></div>
<div class="divide-opacity-50"></div>
<div class="divide-dotted"></div>
<div class="divide-x-2 divide-y-4 divide-x-0 divide-y-0"></div>
<div class="fill-current"></div>
<div class="flex-1"></div>
<div class="flex-row-reverse"></div>
<div class="flex-grow"></div>
<div class="flex-grow-0"></div>
<div class="flex-shrink"></div>
<div class="flex-shrink-0"></div>
<div class="flex-wrap"></div>
<div class="float-right"></div>
<div class="font-sans"></div>
<div class="text-2xl"></div>
<div class="antialiased"></div>
<div class="not-italic"></div>
<div class="tabular-nums ordinal diagonal-fractions"></div>
<div class="font-medium"></div>
<div class="gap-x-2 gap-y-3 gap-4"></div>
<div class="from-red-300 via-purple-200 to-blue-400"></div>
<div class="auto-cols-min"></div>
<div class="grid-flow-row"></div>
<div class="auto-rows-max"></div>
<div class="col-span-3"></div>
<div class="col-start-1"></div>
<div class="col-end-4"></div>
<div class="row-span-2"></div>
<div class="row-start-3"></div>
<div class="row-end-5"></div>
<div class="grid-cols-4"></div>
<div class="grid-rows-3"></div>
<div class="h-16"></div>
<div class="inset-0 inset-y-4 inset-x-2 top-6 right-8 bottom-12 left-16"></div>
<div class="isolate isolation-auto"></div>
<div class="justify-center"></div>
<div class="justify-items-end"></div>
<div class="justify-self-start"></div>
<div class="tracking-tight"></div>
<div class="leading-relaxed leading-5"></div>
<div class="list-inside"></div>
<div class="list-disc"></div>
<div class="m-4 my-2 mx-auto mt-0 mr-1 mb-3 ml-4"></div>
<div class="max-h-screen"></div>
<div class="max-w-full"></div>
<div class="min-h-0"></div>
<div class="min-w-min"></div>
<div class="object-cover"></div>
<div class="object-bottom"></div>
<div class="opacity-90"></div>
<div class="bg-blend-darken bg-blend-difference"></div>
<div class="mix-blend-multiply mix-blend-saturation"></div>
<div class="order-last order-2"></div>
<div class="overflow-hidden"></div>
<div class="overscroll-contain"></div>
<div class="scroll-smooth"></div>
<div class="p-4 py-2 px-3 pt-1 pr-2 pb-3 pl-4"></div>
<div class="place-content-start"></div>
<div class="placeholder-green-300"></div>
<div class="placeholder-opacity-60"></div>
<div class="place-items-end"></div>
<div class="place-self-center"></div>
<div class="pointer-events-none"></div>
<div class="absolute"></div>
<div class="resize-none"></div>
<div class="ring-white"></div>
<div class="ring-offset-blue-300"></div>
<div class="ring-offset-2"></div>
<div class="ring-opacity-40"></div>
<div class="ring ring-4"></div>
<div
class="filter filter-none blur-md brightness-150 contrast-50 drop-shadow-md grayscale hue-rotate-60 invert saturate-200 sepia"
></div>
<div
class="backdrop-filter backdrop-filter-none backdrop-blur-lg backdrop-brightness-50 backdrop-contrast-0 backdrop-grayscale backdrop-hue-rotate-90 backdrop-invert backdrop-opacity-75 backdrop-saturate-150 backdrop-sepia"
></div>
<div class="rotate-3"></div>
<div class="scale-95"></div>
<div class="skew-y-12 skew-x-12"></div>
<div class="space-x-4 space-y-3 space-x-reverse space-y-reverse"></div>
<div class="stroke-current"></div>
<div class="stroke-2"></div>
<div class="table-fixed"></div>
<div class="caption-top"></div>
<div class="caption-bottom"></div>
<div class="text-center"></div>
<div class="text-indigo-500"></div>
<div class="underline"></div>
<div class="text-opacity-10"></div>
<div class="overflow-ellipsis truncate"></div>
<div class="uppercase"></div>
<div class="transform transform-gpu"></div>
<div class="origin-top-right"></div>
<div class="delay-300"></div>
<div class="duration-200"></div>
<div class="transition transition-all"></div>
<div class="ease-in-out"></div>
<div class="translate-x-5 -translate-x-4 translate-y-6 -translate-x-3"></div>
<div class="select-none"></div>
<div class="align-middle"></div>
<div class="invisible"></div>
<div class="collapse"></div>
<div class="whitespace-nowrap"></div>
<div class="w-12"></div>
<div class="break-words"></div>
<div class="z-30"></div>
`,
},
],
corePlugins: { preflight: false },
}
it('raw content', () => {
let config = {
content: [
{
raw: html`
<div class="sr-only"></div>
<div class="content-center"></div>
<div class="items-start"></div>
<div class="self-end"></div>
<div class="animate-none"></div>
<div class="animate-spin"></div>
<div class="appearance-none"></div>
<div class="bg-local"></div>
<div class="bg-clip-border"></div>
<div class="bg-green-500"></div>
<div class="bg-gradient-to-r"></div>
<div class="bg-opacity-20"></div>
<div class="bg-top"></div>
<div class="bg-no-repeat"></div>
<div class="bg-cover"></div>
<div class="bg-origin-border bg-origin-padding bg-origin-content"></div>
<div class="border-collapse"></div>
<div class="border-black"></div>
<div class="border-opacity-10"></div>
<div class="rounded-md"></div>
<div class="border-solid"></div>
<div class="border"></div>
<div class="border-2"></div>
<div class="shadow"></div>
<div class="shadow-md"></div>
<div class="shadow-lg"></div>
<div class="decoration-clone decoration-slice"></div>
<div class="box-border"></div>
<div class="clear-left"></div>
<div class="container"></div>
<div class="cursor-pointer"></div>
<div class="hidden inline-grid"></div>
<div class="divide-gray-200"></div>
<div class="divide-opacity-50"></div>
<div class="divide-dotted"></div>
<div class="divide-x-2 divide-y-4 divide-x-0 divide-y-0"></div>
<div class="fill-current"></div>
<div class="flex-1"></div>
<div class="flex-row-reverse"></div>
<div class="flex-grow"></div>
<div class="flex-grow-0"></div>
<div class="flex-shrink"></div>
<div class="flex-shrink-0"></div>
<div class="flex-wrap"></div>
<div class="float-right"></div>
<div class="font-sans"></div>
<div class="text-2xl"></div>
<div class="antialiased"></div>
<div class="not-italic"></div>
<div class="tabular-nums ordinal diagonal-fractions"></div>
<div class="font-medium"></div>
<div class="gap-x-2 gap-y-3 gap-4"></div>
<div class="from-red-300 via-purple-200 to-blue-400"></div>
<div class="auto-cols-min"></div>
<div class="grid-flow-row"></div>
<div class="auto-rows-max"></div>
<div class="col-span-3"></div>
<div class="col-start-1"></div>
<div class="col-end-4"></div>
<div class="row-span-2"></div>
<div class="row-start-3"></div>
<div class="row-end-5"></div>
<div class="grid-cols-4"></div>
<div class="grid-rows-3"></div>
<div class="h-16"></div>
<div class="inset-0 inset-y-4 inset-x-2 top-6 right-8 bottom-12 left-16"></div>
<div class="isolate isolation-auto"></div>
<div class="justify-center"></div>
<div class="justify-items-end"></div>
<div class="justify-self-start"></div>
<div class="tracking-tight"></div>
<div class="leading-relaxed leading-5"></div>
<div class="list-inside"></div>
<div class="list-disc"></div>
<div class="m-4 my-2 mx-auto mt-0 mr-1 mb-3 ml-4"></div>
<div class="max-h-screen"></div>
<div class="max-w-full"></div>
<div class="min-h-0"></div>
<div class="min-w-min"></div>
<div class="object-cover"></div>
<div class="object-bottom"></div>
<div class="opacity-90"></div>
<div class="bg-blend-darken bg-blend-difference"></div>
<div class="mix-blend-multiply mix-blend-saturation"></div>
<div class="order-last order-2"></div>
<div class="overflow-hidden"></div>
<div class="overscroll-contain"></div>
<div class="scroll-smooth"></div>
<div class="p-4 py-2 px-3 pt-1 pr-2 pb-3 pl-4"></div>
<div class="place-content-start"></div>
<div class="placeholder-green-300"></div>
<div class="placeholder-opacity-60"></div>
<div class="place-items-end"></div>
<div class="place-self-center"></div>
<div class="pointer-events-none"></div>
<div class="absolute"></div>
<div class="resize-none"></div>
<div class="ring-white"></div>
<div class="ring-offset-blue-300"></div>
<div class="ring-offset-2"></div>
<div class="ring-opacity-40"></div>
<div class="ring ring-4"></div>
<div
class="filter filter-none blur-md brightness-150 contrast-50 drop-shadow-md grayscale hue-rotate-60 invert saturate-200 sepia"
></div>
<div
class="backdrop-filter backdrop-filter-none backdrop-blur-lg backdrop-brightness-50 backdrop-contrast-0 backdrop-grayscale backdrop-hue-rotate-90 backdrop-invert backdrop-opacity-75 backdrop-saturate-150 backdrop-sepia"
></div>
<div class="rotate-3"></div>
<div class="scale-95"></div>
<div class="skew-y-12 skew-x-12"></div>
<div class="space-x-4 space-y-3 space-x-reverse space-y-reverse"></div>
<div class="stroke-current"></div>
<div class="stroke-2"></div>
<div class="table-fixed"></div>
<div class="caption-top"></div>
<div class="caption-bottom"></div>
<div class="text-center"></div>
<div class="text-indigo-500"></div>
<div class="underline"></div>
<div class="text-opacity-10"></div>
<div class="overflow-ellipsis truncate"></div>
<div class="uppercase"></div>
<div class="transform transform-gpu"></div>
<div class="origin-top-right"></div>
<div class="delay-300"></div>
<div class="duration-200"></div>
<div class="transition transition-all"></div>
<div class="ease-in-out"></div>
<div class="translate-x-5 -translate-x-4 translate-y-6 -translate-x-3"></div>
<div class="select-none"></div>
<div class="align-middle"></div>
<div class="invisible"></div>
<div class="collapse"></div>
<div class="whitespace-nowrap"></div>
<div class="w-12"></div>
<div class="break-words"></div>
<div class="z-30"></div>
`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind components;
@tailwind utilities;
`
let input = css`
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
stable
.expect(result.css)
.toMatchFormattedCss(
fs.readFileSync(path.resolve(__dirname, './raw-content.test.css'), 'utf8')
)
})
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(
fs.readFileSync(path.resolve(__dirname, './raw-content.test.css'), 'utf8')
)
})
})
+17 -19
View File
@@ -1,24 +1,22 @@
import { crosscheck, run, css } from './util/run'
import { run, css } from './util/run'
crosscheck(() => {
test('relative purge paths', () => {
let config = {
content: ['./tests/relative-purge-paths.test.html'],
corePlugins: { preflight: false },
}
test('relative purge paths', () => {
let config = {
content: ['./tests/relative-purge-paths.test.html'],
corePlugins: { preflight: false },
}
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
let input = css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toIncludeCss(css`
.font-bold {
font-weight: 700;
}
`)
})
return run(input, config).then((result) => {
expect(result.css).toIncludeCss(css`
.font-bold {
font-weight: 700;
}
`)
})
})
File diff suppressed because it is too large Load Diff
+1575 -1578
View File
File diff suppressed because it is too large Load Diff
+131 -133
View File
@@ -1,157 +1,155 @@
import { crosscheck, run, html, css } from './util/run'
import { run, html, css } from './util/run'
crosscheck(() => {
test('responsive and variants atrules', () => {
let config = {
content: [
{
raw: html`
<div class="responsive-in-utilities"></div>
<div class="variants-in-utilities"></div>
<div class="both-in-utilities"></div>
<div class="responsive-at-root"></div>
<div class="variants-at-root"></div>
<div class="both-at-root"></div>
<div class="responsive-in-components"></div>
<div class="variants-in-components"></div>
<div class="both-in-components"></div>
<div class="md:focus:responsive-in-utilities"></div>
<div class="md:focus:variants-in-utilities"></div>
<div class="md:focus:both-in-utilities"></div>
<div class="md:focus:responsive-at-root"></div>
<div class="md:focus:variants-at-root"></div>
<div class="md:focus:both-at-root"></div>
<div class="md:focus:responsive-in-components"></div>
<div class="md:focus:variants-in-components"></div>
<div class="md:focus:both-in-components"></div>
`,
},
],
corePlugins: { preflight: false },
}
test('responsive and variants atrules', () => {
let config = {
content: [
{
raw: html`
<div class="responsive-in-utilities"></div>
<div class="variants-in-utilities"></div>
<div class="both-in-utilities"></div>
<div class="responsive-at-root"></div>
<div class="variants-at-root"></div>
<div class="both-at-root"></div>
<div class="responsive-in-components"></div>
<div class="variants-in-components"></div>
<div class="both-in-components"></div>
<div class="md:focus:responsive-in-utilities"></div>
<div class="md:focus:variants-in-utilities"></div>
<div class="md:focus:both-in-utilities"></div>
<div class="md:focus:responsive-at-root"></div>
<div class="md:focus:variants-at-root"></div>
<div class="md:focus:both-at-root"></div>
<div class="md:focus:responsive-in-components"></div>
<div class="md:focus:variants-in-components"></div>
<div class="md:focus:both-in-components"></div>
`,
},
],
corePlugins: { preflight: false },
}
let input = css`
@tailwind components;
@tailwind utilities;
@layer utilities {
@responsive {
.responsive-in-utilities {
color: blue;
}
}
@variants {
.variants-in-utilities {
color: red;
}
}
@responsive {
@variants {
.both-in-utilities {
color: green;
}
}
}
}
let input = css`
@tailwind components;
@tailwind utilities;
@layer utilities {
@responsive {
.responsive-at-root {
color: white;
.responsive-in-utilities {
color: blue;
}
}
@variants {
.variants-at-root {
color: orange;
.variants-in-utilities {
color: red;
}
}
@responsive {
@variants {
.both-at-root {
color: pink;
.both-in-utilities {
color: green;
}
}
}
}
@layer components {
@responsive {
.responsive-in-components {
color: blue;
}
}
@variants {
.variants-in-components {
color: red;
}
}
@responsive {
@variants {
.both-in-components {
color: green;
}
}
}
@responsive {
.responsive-at-root {
color: white;
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.responsive-in-components {
color: #00f;
}
.variants-in-components {
color: red;
}
.both-in-components {
color: green;
}
.responsive-in-utilities {
color: #00f;
}
.variants-in-utilities {
color: red;
}
.both-in-utilities {
color: green;
}
.responsive-at-root {
color: #fff;
}
.variants-at-root {
color: orange;
}
}
@variants {
.variants-at-root {
color: orange;
}
}
@responsive {
@variants {
.both-at-root {
color: pink;
}
@media (min-width: 768px) {
.md\:focus\:responsive-in-components:focus {
color: #00f;
}
.md\:focus\:variants-in-components:focus {
color: red;
}
.md\:focus\:both-in-components:focus {
}
}
@layer components {
@responsive {
.responsive-in-components {
color: blue;
}
}
@variants {
.variants-in-components {
color: red;
}
}
@responsive {
@variants {
.both-in-components {
color: green;
}
.md\:focus\:responsive-in-utilities:focus {
color: #00f;
}
.md\:focus\:variants-in-utilities:focus {
color: red;
}
.md\:focus\:both-in-utilities:focus {
color: green;
}
.md\:focus\:responsive-at-root:focus {
color: #fff;
}
.md\:focus\:variants-at-root:focus {
color: orange;
}
.md\:focus\:both-at-root:focus {
color: pink;
}
}
`)
})
}
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.responsive-in-components {
color: #00f;
}
.variants-in-components {
color: red;
}
.both-in-components {
color: green;
}
.responsive-in-utilities {
color: #00f;
}
.variants-in-utilities {
color: red;
}
.both-in-utilities {
color: green;
}
.responsive-at-root {
color: #fff;
}
.variants-at-root {
color: orange;
}
.both-at-root {
color: pink;
}
@media (min-width: 768px) {
.md\:focus\:responsive-in-components:focus {
color: #00f;
}
.md\:focus\:variants-in-components:focus {
color: red;
}
.md\:focus\:both-in-components:focus {
color: green;
}
.md\:focus\:responsive-in-utilities:focus {
color: #00f;
}
.md\:focus\:variants-in-utilities:focus {
color: red;
}
.md\:focus\:both-in-utilities:focus {
color: green;
}
.md\:focus\:responsive-at-root:focus {
color: #fff;
}
.md\:focus\:variants-at-root:focus {
color: orange;
}
.md\:focus\:both-at-root:focus {
color: pink;
}
}
`)
})
})
+448 -593
View File
File diff suppressed because it is too large Load Diff
+37 -39
View File
@@ -1,60 +1,58 @@
import postcss from 'postcss'
import plugin from '../src/lib/substituteScreenAtRules'
import config from '../stubs/config.full.js'
import { crosscheck, css } from './util/run'
import { css } from './util/run'
function run(input, opts = config) {
return postcss([plugin({ tailwindConfig: opts })]).process(input, { from: undefined })
}
crosscheck(() => {
test('it can generate media queries from configured screen sizes', () => {
let input = css`
@screen sm {
test('it can generate media queries from configured screen sizes', () => {
let input = css`
@screen sm {
.banana {
color: yellow;
}
}
@screen md {
.banana {
color: red;
}
}
@screen lg {
.banana {
color: green;
}
}
`
return run(input, {
theme: {
screens: {
sm: '500px',
md: '750px',
lg: '1000px',
},
},
separator: ':',
}).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@media (min-width: 500px) {
.banana {
color: yellow;
color: #ff0;
}
}
@screen md {
@media (min-width: 750px) {
.banana {
color: red;
}
}
@screen lg {
@media (min-width: 1000px) {
.banana {
color: green;
}
}
`
return run(input, {
theme: {
screens: {
sm: '500px',
md: '750px',
lg: '1000px',
},
},
separator: ':',
}).then((result) => {
expect(result.css).toMatchFormattedCss(css`
@media (min-width: 500px) {
.banana {
color: #ff0;
}
}
@media (min-width: 750px) {
.banana {
color: red;
}
}
@media (min-width: 1000px) {
.banana {
color: green;
}
}
`)
expect(result.warnings().length).toBe(0)
})
`)
expect(result.warnings().length).toBe(0)
})
})
+17 -20
View File
@@ -1,23 +1,20 @@
import { resolveDebug } from '../src/lib/sharedState'
import { crosscheck } from './util/run'
crosscheck(() => {
it.each`
value | expected
${'true'} | ${true}
${'1'} | ${true}
${'false'} | ${false}
${'0'} | ${false}
${'*'} | ${true}
${'tailwindcss'} | ${true}
${'tailwindcss:*'} | ${true}
${'other,tailwindcss'} | ${true}
${'other,tailwindcss:*'} | ${true}
${'other,-tailwindcss'} | ${false}
${'other,-tailwindcss:*'} | ${false}
${'-tailwindcss'} | ${false}
${'-tailwindcss:*'} | ${false}
`('should resolve the debug ($value) flag correctly ($expected)', ({ value, expected }) => {
expect(resolveDebug(value)).toBe(expected)
})
it.each`
value | expected
${'true'} | ${true}
${'1'} | ${true}
${'false'} | ${false}
${'0'} | ${false}
${'*'} | ${true}
${'tailwindcss'} | ${true}
${'tailwindcss:*'} | ${true}
${'other,tailwindcss'} | ${true}
${'other,tailwindcss:*'} | ${true}
${'other,-tailwindcss'} | ${false}
${'other,-tailwindcss:*'} | ${false}
${'-tailwindcss'} | ${false}
${'-tailwindcss:*'} | ${false}
`('should resolve the debug ($value) flag correctly ($expected)', ({ value, expected }) => {
expect(resolveDebug(value)).toBe(expected)
})
+532 -547
View File
File diff suppressed because it is too large Load Diff
+41 -68
View File
@@ -1,72 +1,45 @@
import { crosscheck, run, css } from './util/run'
import { run, css } from './util/run'
crosscheck(({ stable, oxide }) => {
test('it detects classes in lit-html templates', () => {
let config = {
content: [
{
raw: `html\`<button class="bg-blue-400 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded">\${data.title}</button>\`;`,
},
],
corePlugins: { preflight: false },
theme: {},
plugins: [],
}
test('it detects classes in lit-html templates', () => {
let config = {
content: [
{
raw: `html\`<button class="bg-blue-400 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded">\${data.title}</button>\`;`,
},
],
corePlugins: { preflight: false },
theme: {},
plugins: [],
}
return run('@tailwind utilities', config).then((result) => {
stable.expect(result.css).toMatchFormattedCss(css`
.rounded {
border-radius: 0.25rem;
}
.bg-blue-400 {
--tw-bg-opacity: 1;
background-color: rgb(96 165 250 / var(--tw-bg-opacity));
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.py-2 {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.font-bold {
font-weight: 700;
}
.text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.hover\:bg-blue-600:hover {
--tw-bg-opacity: 1;
background-color: rgb(37 99 235 / var(--tw-bg-opacity));
}
`)
oxide.expect(result.css).toMatchFormattedCss(css`
.rounded {
border-radius: 0.25rem;
}
.bg-blue-400 {
background-color: #60a5fa;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.py-2 {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.font-bold {
font-weight: 700;
}
.text-white {
color: #fff;
}
.hover\:bg-blue-600:hover {
background-color: #2563eb;
}
`)
})
return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.rounded {
border-radius: 0.25rem;
}
.bg-blue-400 {
--tw-bg-opacity: 1;
background-color: rgb(96 165 250 / var(--tw-bg-opacity));
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.py-2 {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.font-bold {
font-weight: 700;
}
.text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.hover\:bg-blue-600:hover {
--tw-bg-opacity: 1;
background-color: rgb(37 99 235 / var(--tw-bg-opacity));
}
`)
})
})
+60 -64
View File
@@ -1,81 +1,77 @@
import path from 'path'
import { crosscheck, run, css } from './util/run'
import { run, css } from './util/run'
crosscheck(({ stable, oxide }) => {
oxide.test.todo('it detects svelte based on the file extension')
stable.test('it detects svelte based on the file extension', () => {
let config = {
content: [path.resolve(__dirname, './syntax-svelte.test.svelte')],
corePlugins: { preflight: false },
theme: {},
plugins: [],
}
test('it detects svelte based on the file extension', () => {
let config = {
content: [path.resolve(__dirname, './syntax-svelte.test.svelte')],
corePlugins: { preflight: false },
theme: {},
plugins: [],
}
let input = css`
@tailwind components;
@tailwind utilities;
`
let input = css`
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchCss(css`
.bg-red-500 {
return run(input, config).then((result) => {
expect(result.css).toMatchCss(css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
@media (min-width: 1024px) {
.lg\:hover\:bg-blue-500:hover {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
background-color: rgb(59 130 246 / var(--tw-bg-opacity));
}
@media (min-width: 1024px) {
.lg\:hover\:bg-blue-500:hover {
--tw-bg-opacity: 1;
background-color: rgb(59 130 246 / var(--tw-bg-opacity));
}
}
`)
})
}
`)
})
})
oxide.test.todo('using raw with svelte extension')
stable.test('using raw with svelte extension', () => {
let config = {
content: [
{
raw: `
<script>
let current = 'foo'
</script>
test('using raw with svelte extension', () => {
let config = {
content: [
{
raw: `
<script>
let current = 'foo'
</script>
<button class:lg:hover:bg-blue-500={current === 'foo'}>Click me</button>
<button class:lg:hover:bg-blue-500={current === 'foo'}>Click me</button>
<button
class:bg-red-500={current === 'foo'}
>
Click me
</button>
<button
class:bg-red-500={current === 'foo'}
>
Click me
</button>
`,
extension: 'svelte',
},
],
corePlugins: { preflight: false },
theme: {},
plugins: [],
}
extension: 'svelte',
},
],
corePlugins: { preflight: false },
theme: {},
plugins: [],
}
let input = css`
@tailwind components;
@tailwind utilities;
`
let input = css`
@tailwind components;
@tailwind utilities;
`
return run(input, config).then((result) => {
expect(result.css).toMatchCss(css`
.bg-red-500 {
return run(input, config).then((result) => {
expect(result.css).toMatchCss(css`
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
@media (min-width: 1024px) {
.lg\:hover\:bg-blue-500:hover {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
}
@media (min-width: 1024px) {
.lg\:hover\:bg-blue-500:hover {
--tw-bg-opacity: 1;
background-color: rgb(59 130 246 / var(--tw-bg-opacity));
}
background-color: rgb(59 130 246 / var(--tw-bg-opacity));
}
`)
})
}
`)
})
})
+51 -53
View File
@@ -1,65 +1,63 @@
import { crosscheck, run, html, css } from './util/run'
import { run, html, css } from './util/run'
crosscheck(() => {
test('class variants are inserted at `@tailwind variants`', async () => {
let config = {
content: [{ raw: html`<div class="font-bold hover:font-bold md:font-bold"></div>` }],
test('class variants are inserted at `@tailwind variants`', async () => {
let config = {
content: [{ raw: html`<div class="font-bold hover:font-bold md:font-bold"></div>` }],
}
let input = css`
@tailwind utilities;
@tailwind variants;
.foo {
color: black;
}
`
let input = css`
@tailwind utilities;
@tailwind variants;
.foo {
color: black;
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-bold,
.hover\:font-bold:hover {
font-weight: 700;
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-bold,
.hover\:font-bold:hover {
@media (min-width: 768px) {
.md\:font-bold {
font-weight: 700;
}
@media (min-width: 768px) {
.md\:font-bold {
font-weight: 700;
}
}
.foo {
color: #000;
}
`)
})
})
test('`@tailwind screens` works as an alias for `@tailwind variants`', async () => {
let config = {
content: [{ raw: html`<div class="font-bold hover:font-bold md:font-bold"></div>` }],
}
let input = css`
@tailwind utilities;
@tailwind screens;
.foo {
color: black;
}
`
.foo {
color: #000;
}
`)
})
})
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-bold,
.hover\:font-bold:hover {
test('`@tailwind screens` works as an alias for `@tailwind variants`', async () => {
let config = {
content: [{ raw: html`<div class="font-bold hover:font-bold md:font-bold"></div>` }],
}
let input = css`
@tailwind utilities;
@tailwind screens;
.foo {
color: black;
}
`
return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-bold,
.hover\:font-bold:hover {
font-weight: 700;
}
@media (min-width: 768px) {
.md\:font-bold {
font-weight: 700;
}
@media (min-width: 768px) {
.md\:font-bold {
font-weight: 700;
}
}
.foo {
color: #000;
}
`)
})
}
.foo {
color: #000;
}
`)
})
})

Some files were not shown because too many files have changed in this diff Show More