diff --git a/integrations/parcel/tests/integration.test.js b/integrations/parcel/tests/integration.test.js index 38cada21ddf5..9a9091a30589 100644 --- a/integrations/parcel/tests/integration.test.js +++ b/integrations/parcel/tests/integration.test.js @@ -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`
`) }) - 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`
`) }) - 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() }) diff --git a/integrations/postcss-cli/tests/integration.test.js b/integrations/postcss-cli/tests/integration.test.js index 46f5a61a6dc3..964c57b82e39 100644 --- a/integrations/postcss-cli/tests/integration.test.js +++ b/integrations/postcss-cli/tests/integration.test.js @@ -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`
`) 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`
`) 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() }) diff --git a/integrations/rollup-sass/tests/integration.test.js b/integrations/rollup-sass/tests/integration.test.js index a5189cd6454d..8793784c6db6 100644 --- a/integrations/rollup-sass/tests/integration.test.js +++ b/integrations/rollup-sass/tests/integration.test.js @@ -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`
`) 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`
`) 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) diff --git a/integrations/rollup/tests/integration.test.js b/integrations/rollup/tests/integration.test.js index c3209af6589a..86544ffe1ec0 100644 --- a/integrations/rollup/tests/integration.test.js +++ b/integrations/rollup/tests/integration.test.js @@ -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`
`) 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`
`) 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() }) diff --git a/integrations/tailwindcss-cli/tests/cli.test.js b/integrations/tailwindcss-cli/tests/cli.test.js index 0c42ce0cc506..bd15994fd957 100644 --- a/integrations/tailwindcss-cli/tests/cli.test.js +++ b/integrations/tailwindcss-cli/tests/cli.test.js @@ -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`
`) 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`
`) 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; - } - - .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; - } + 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`
`) 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; - } - - .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; - } + 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`
`) 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`
`) 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`
`) @@ -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} - - Usage: - tailwindcss init [options] + expect(dedent(combined)).toEqual( + dedent(` + tailwindcss v${version} - 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] - 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 () => { diff --git a/integrations/tailwindcss-cli/tests/integration.test.js b/integrations/tailwindcss-cli/tests/integration.test.js index 75c9779af8f8..b53f8afaf388 100644 --- a/integrations/tailwindcss-cli/tests/integration.test.js +++ b/integrations/tailwindcss-cli/tests/integration.test.js @@ -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)); - } - - .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; - } + 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 { - background-color: #dc2626; - } + .bg-red-600 { + --tw-bg-opacity: 1; + background-color: rgb(220 38 38 / var(--tw-bg-opacity)); + } - .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`
`) 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`
`) 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() }) diff --git a/integrations/vite/tests/integration.test.js b/integrations/vite/tests/integration.test.js index a9687660aa06..1a3b1302b3a7 100644 --- a/integrations/vite/tests/integration.test.js +++ b/integrations/vite/tests/integration.test.js @@ -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`
`) 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`
`) 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() }) diff --git a/integrations/webpack-4/tests/integration.test.js b/integrations/webpack-4/tests/integration.test.js index e2e24cf45cac..ae7cce0c8a2d 100644 --- a/integrations/webpack-4/tests/integration.test.js +++ b/integrations/webpack-4/tests/integration.test.js @@ -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`
`) }) - 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`
`) }) - 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() }) diff --git a/integrations/webpack-5/tests/integration.test.js b/integrations/webpack-5/tests/integration.test.js index 83b42f5f867f..1a4a969a4a38 100644 --- a/integrations/webpack-5/tests/integration.test.js +++ b/integrations/webpack-5/tests/integration.test.js @@ -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`
`) }) - 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`
`) }) - 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)); - } - - .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; - } + 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 { - background-color: #dc2626; - } + .bg-red-600 { + --tw-bg-opacity: 1; + background-color: rgb(220 38 38 / var(--tw-bg-opacity)); + } - .font-bold { - font-weight: 700; - } - ` - ) - } + .font-bold { + font-weight: 700; + } + ` + ) return runningProcess.stop() }) diff --git a/package-lock.json b/package-lock.json index b6ba959174e9..5221261ab908 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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 }, diff --git a/package.json b/package.json index 09647fe3171d..b14fe56c00d7 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/cli.js b/src/cli.js index 4b73accbbbdd..9a5aff27bdf7 100644 --- a/src/cli.js +++ b/src/cli.js @@ -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') diff --git a/src/cli/build/plugin.js b/src/cli/build/plugin.js index 0bef4d649728..49ffdcb0ccd3 100644 --- a/src/cli/build/plugin.js +++ b/src/cli/build/plugin.js @@ -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 diff --git a/src/corePlugins.js b/src/corePlugins.js index df52c1d9c4c0..48e93eefec08 100644 --- a/src/corePlugins.js +++ b/src/corePlugins.js @@ -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': {}, diff --git a/src/featureFlags.js b/src/featureFlags.js index 69fa569ffb59..0a1fca8ce919 100644 --- a/src/featureFlags.js +++ b/src/featureFlags.js @@ -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 = { diff --git a/src/lib/expandTailwindAtRules.js b/src/lib/expandTailwindAtRules.js index b78bf1e2b08b..ab2f7f7ad865 100644 --- a/src/lib/expandTailwindAtRules.js +++ b/src/lib/expandTailwindAtRules.js @@ -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) - } - - // 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 = [] + /** @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') diff --git a/src/lib/sharedState.js b/src/lib/sharedState.js index 97bdf09c5c43..915dd21cbce2 100644 --- a/src/lib/sharedState.js +++ b/src/lib/sharedState.js @@ -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() diff --git a/src/plugin.js b/src/plugin.js index bbb8cc14dd37..63970f00eecc 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -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') diff --git a/standalone-cli/package-lock.json b/standalone-cli/package-lock.json index b455e85044b8..9c0309d53d9c 100644 --- a/standalone-cli/package-lock.json +++ b/standalone-cli/package-lock.json @@ -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", diff --git a/tests/animations.test.js b/tests/animations.test.js index b8d0d2406306..19e695076729 100644 --- a/tests/animations.test.js +++ b/tests/animations.test.js @@ -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` -
-
-
- `, - }, - ], - } +test('basic', () => { + let config = { + content: [ + { + raw: html` +
+
+
+ `, + }, + ], + } - 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; - } - @keyframes ping { - 75%, - 100% { - opacity: 0; - transform: scale(2); - } + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + @keyframes spin { + to { + transform: rotate(360deg); } - .hover\:animate-ping:hover { - animation: 1s cubic-bezier(0, 0, 0.2, 1) infinite ping; + } + .animate-spin { + animation: 1s linear infinite spin; + } + @keyframes ping { + 75%, + 100% { + opacity: 0; + transform: scale(2); } - @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; - } + } + .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%); } - .group:hover .group-hover\:animate-bounce { - animation: 1s infinite bounce; + 50% { + animation-timing-function: cubic-bezier(0, 0, 0.2, 1); + transform: none; } - `) - }) + } + .group:hover .group-hover\:animate-bounce { + animation: 1s infinite bounce; + } + `) }) +}) - test('custom', () => { - let config = { - content: [{ raw: html`
` }], - theme: { - extend: { - keyframes: { - one: { to: { transform: 'rotate(360deg)' } }, - }, - animation: { - one: 'one 2s', - }, +test('custom', () => { + let config = { + content: [{ raw: html`
` }], + 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); - } - } - .animate-one { - animation: 2s one; + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + @keyframes one { + to { + transform: rotate(360deg); } - `) - }) + } + .animate-one { + animation: 2s one; + } + `) }) +}) - test('custom prefixed', () => { - let config = { - prefix: 'tw-', - content: [{ raw: `
` }], - theme: { - extend: { - keyframes: { - one: { to: { transform: 'rotate(360deg)' } }, - }, - animation: { - one: 'one 2s', - }, +test('custom prefixed', () => { + let config = { + prefix: 'tw-', + content: [{ raw: `
` }], + 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); - } - } - .tw-animate-one { - animation: 2s tw-one; + 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; + } + `) }) +}) - test('multiple', () => { - let config = { - content: [{ raw: html`
` }], - theme: { - extend: { - animation: { - multiple: 'bounce 2s linear, pulse 3s ease-in', - }, +test('multiple', () => { + let config = { + content: [{ raw: html`
` }], + 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`
` }], - 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`
` }], + 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); - } - } - @keyframes two { - to { - transform: scale(1.23); - } + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + @keyframes one { + to { + transform: rotate(360deg); } - .animate-multiple { - animation: 2s one, 3s two; + } + @keyframes two { + to { + transform: scale(1.23); } - `) - }) + } + .animate-multiple { + animation: 2s one, 3s two; + } + `) }) +}) - test('with dots in the name', () => { - let config = { - content: [ - { - raw: html` -
-
- `, +test('with dots in the name', () => { + let config = { + content: [ + { + raw: html` +
+
+ `, + }, + ], + 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); - } - } - .animate-zoom-\.5 { - animation: 2s zoom-\.5; - } - @keyframes zoom-1\.5 { - to { - transform: scale(1.5); - } + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + @keyframes zoom-\.5 { + to { + transform: scale(0.5); } - .animate-zoom-1\.5 { - animation: 2s zoom-1\.5; + } + .animate-zoom-\.5 { + animation: 2s zoom-\.5; + } + @keyframes zoom-1\.5 { + to { + transform: scale(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` -
-
- `, +test('with dots in the name and prefix', () => { + let config = { + prefix: 'tw-', + content: [ + { + raw: html` +
+
+ `, + }, + ], + 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); - } - } - .tw-animate-zoom-\.5 { - animation: 2s tw-zoom-\.5; - } - @keyframes tw-zoom-1\.5 { - to { - transform: scale(1.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-1\.5 { - animation: 2s tw-zoom-1\.5; + } + .tw-animate-zoom-\.5 { + animation: 2s tw-zoom-\.5; + } + @keyframes tw-zoom-1\.5 { + to { + transform: scale(1.5); } - `) - }) + } + .tw-animate-zoom-1\.5 { + animation: 2s tw-zoom-1\.5; + } + `) }) }) diff --git a/tests/any-type.test.js b/tests/any-type.test.js index 7b787396647c..975cfa40ebed 100644 --- a/tests/any-type.test.js +++ b/tests/any-type.test.js @@ -1,1307 +1,756 @@ -import { crosscheck, run, html, css } from './util/run' +import { run, html, css } from './util/run' -crosscheck(({ stable, oxide }) => { - // Hi there, so you are debugging this test because something failed... right? Well we can look into - // the future and guessed that this would happen. So basically it means that we (it was probably - // you, silly) introduced a new plugin that conflicts with an existing plugin that has (either - // implicit or explicit) an `any` type. - // - // Now it is your job to decide which one should win, and mark that one with - // ```diff - // - 'any' - // + ['any', { preferOnConflict: true }] - // ``` - // in the corePlugins.js file. - // - // You probably want to let the original one win for backwards compatible reasons. - // - // Good luck! - test('any types are set on correct plugins', () => { - let config = { - content: [ - { - raw: html` -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - } +// Hi there, so you are debugging this test because something failed... right? Well we can look into +// the future and guessed that this would happen. So basically it means that we (it was probably +// you, silly) introduced a new plugin that conflicts with an existing plugin that has (either +// implicit or explicit) an `any` type. +// +// Now it is your job to decide which one should win, and mark that one with +// ```diff +// - 'any' +// + ['any', { preferOnConflict: true }] +// ``` +// in the corePlugins.js file. +// +// You probably want to let the original one win for backwards compatible reasons. +// +// Good luck! +test('any types are set on correct plugins', () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - return run(input, config).then((result) => { - oxide.expect(result.css).toMatchFormattedCss(css` - .inset-\[var\(--any-value\)\] { - inset: var(--any-value); - } - .inset-x-\[var\(--any-value\)\] { - left: var(--any-value); - right: var(--any-value); - } - .inset-y-\[var\(--any-value\)\] { - top: var(--any-value); - bottom: var(--any-value); - } - .bottom-\[var\(--any-value\)\] { - bottom: var(--any-value); - } - .left-\[var\(--any-value\)\] { - left: var(--any-value); - } - .right-\[var\(--any-value\)\] { - right: var(--any-value); - } - .top-\[var\(--any-value\)\] { - top: var(--any-value); - } - .z-\[var\(--any-value\)\] { - z-index: var(--any-value); - } - .order-\[var\(--any-value\)\] { - order: var(--any-value); - } - .col-\[var\(--any-value\)\] { - grid-column: var(--any-value); - } - .col-start-\[var\(--any-value\)\] { - grid-column-start: var(--any-value); - } - .col-end-\[var\(--any-value\)\] { - grid-column-end: var(--any-value); - } - .row-\[var\(--any-value\)\] { - grid-row: var(--any-value); - } - .row-start-\[var\(--any-value\)\] { - grid-row-start: var(--any-value); - } - .row-end-\[var\(--any-value\)\] { - grid-row-end: var(--any-value); - } - .m-\[var\(--any-value\)\] { - margin: var(--any-value); - } - .mx-\[var\(--any-value\)\] { - margin-left: var(--any-value); - margin-right: var(--any-value); - } - .my-\[var\(--any-value\)\] { - margin-top: var(--any-value); - margin-bottom: var(--any-value); - } - .mb-\[var\(--any-value\)\] { - margin-bottom: var(--any-value); - } - .ml-\[var\(--any-value\)\] { - margin-left: var(--any-value); - } - .mr-\[var\(--any-value\)\] { - margin-right: var(--any-value); - } - .mt-\[var\(--any-value\)\] { - margin-top: var(--any-value); - } - .aspect-\[var\(--any-value\)\] { - aspect-ratio: var(--any-value); - } - .h-\[var\(--any-value\)\] { - height: var(--any-value); - } - .max-h-\[var\(--any-value\)\] { - max-height: var(--any-value); - } - .min-h-\[var\(--any-value\)\] { - min-height: var(--any-value); - } - .w-\[var\(--any-value\)\] { - width: var(--any-value); - } - .min-w-\[var\(--any-value\)\] { - min-width: var(--any-value); - } - .max-w-\[var\(--any-value\)\] { - max-width: var(--any-value); - } - .flex-\[var\(--any-value\)\] { - flex: var(--any-value); - } - .flex-shrink-\[var\(--any-value\)\], - .shrink-\[var\(--any-value\)\] { - flex-shrink: var(--any-value); - } - .flex-grow-\[var\(--any-value\)\], - .grow-\[var\(--any-value\)\] { - flex-grow: var(--any-value); - } - .basis-\[var\(--any-value\)\] { - flex-basis: var(--any-value); - } - .border-spacing-\[var\(--any-value\)\] { - --tw-border-spacing-x: var(--any-value); - --tw-border-spacing-y: var(--any-value); - border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y); - } - .border-spacing-x-\[var\(--any-value\)\] { - --tw-border-spacing-x: var(--any-value); - border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y); - } - .border-spacing-y-\[var\(--any-value\)\] { - --tw-border-spacing-y: var(--any-value); - border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y); - } - .origin-\[var\(--any-value\)\] { - transform-origin: var(--any-value); - } - .translate-x-\[var\(--any-value\)\] { - --tw-translate-x: var(--any-value); - 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)); - } - .translate-y-\[var\(--any-value\)\] { - --tw-translate-y: var(--any-value); - 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)); - } - .rotate-\[var\(--any-value\)\] { - --tw-rotate: var(--any-value); - 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)); - } - .scale-\[var\(--any-value\)\] { - --tw-scale-x: var(--any-value); - --tw-scale-y: var(--any-value); - 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)); - } - .scale-x-\[var\(--any-value\)\] { - --tw-scale-x: var(--any-value); - 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)); - } - .scale-y-\[var\(--any-value\)\] { - --tw-scale-y: var(--any-value); - 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)); - } - .animate-\[var\(--any-value\)\] { - animation: var(--any-value); - } - .cursor-\[var\(--any-value\)\] { - cursor: var(--any-value); - } - .scroll-m-\[var\(--any-value\)\] { - scroll-margin: var(--any-value); - } - .scroll-mx-\[var\(--any-value\)\] { - scroll-margin-left: var(--any-value); - scroll-margin-right: var(--any-value); - } - .scroll-my-\[var\(--any-value\)\] { - scroll-margin-top: var(--any-value); - scroll-margin-bottom: var(--any-value); - } - .scroll-mb-\[var\(--any-value\)\] { - scroll-margin-bottom: var(--any-value); - } - .scroll-ml-\[var\(--any-value\)\] { - scroll-margin-left: var(--any-value); - } - .scroll-mr-\[var\(--any-value\)\] { - scroll-margin-right: var(--any-value); - } - .scroll-mt-\[var\(--any-value\)\] { - scroll-margin-top: var(--any-value); - } - .scroll-p-\[var\(--any-value\)\] { - scroll-padding: var(--any-value); - } - .scroll-px-\[var\(--any-value\)\] { - scroll-padding-left: var(--any-value); - scroll-padding-right: var(--any-value); - } - .scroll-py-\[var\(--any-value\)\] { - scroll-padding-top: var(--any-value); - scroll-padding-bottom: var(--any-value); - } - .scroll-pb-\[var\(--any-value\)\] { - scroll-padding-bottom: var(--any-value); - } - .scroll-pl-\[var\(--any-value\)\] { - scroll-padding-left: var(--any-value); - } - .scroll-pr-\[var\(--any-value\)\] { - scroll-padding-right: var(--any-value); - } - .scroll-pt-\[var\(--any-value\)\] { - scroll-padding-top: var(--any-value); - } - .list-\[var\(--any-value\)\] { - list-style-type: var(--any-value); - } - .columns-\[var\(--any-value\)\] { - columns: var(--any-value); - } - .auto-cols-\[var\(--any-value\)\] { - grid-auto-columns: var(--any-value); - } - .auto-rows-\[var\(--any-value\)\] { - grid-auto-rows: var(--any-value); - } - .grid-cols-\[var\(--any-value\)\] { - grid-template-columns: var(--any-value); - } - .grid-rows-\[var\(--any-value\)\] { - grid-template-rows: var(--any-value); - } - .gap-\[var\(--any-value\)\] { - gap: var(--any-value); - } - .gap-x-\[var\(--any-value\)\] { - column-gap: var(--any-value); - } - .gap-y-\[var\(--any-value\)\] { - row-gap: var(--any-value); - } - .space-x-\[var\(--any-value\)\] > :not([hidden]) ~ :not([hidden]) { - --tw-space-x-reverse: 0; - margin-inline-start: calc(var(--any-value) * calc(1 - var(--tw-space-x-reverse))); - margin-inline-end: calc(var(--any-value) * var(--tw-space-x-reverse)); - } - .space-y-\[var\(--any-value\)\] > :not([hidden]) ~ :not([hidden]) { - --tw-space-y-reverse: 0; - margin-top: calc(var(--any-value) * calc(1 - var(--tw-space-y-reverse))); - margin-bottom: calc(var(--any-value) * var(--tw-space-y-reverse)); - } - .divide-y-\[var\(--any-value\)\] > :not([hidden]) ~ :not([hidden]) { - --tw-divide-y-reverse: 0; - border-top-width: calc(var(--any-value) * calc(1 - var(--tw-divide-y-reverse))); - border-bottom-width: calc(var(--any-value) * var(--tw-divide-y-reverse)); - } - .divide-\[var\(--any-value\)\] > :not([hidden]) ~ :not([hidden]) { - border-color: var(--any-value); - } - .rounded-\[var\(--any-value\)\] { - border-radius: var(--any-value); - } - .rounded-b-\[var\(--any-value\)\] { - border-bottom-right-radius: var(--any-value); - border-bottom-left-radius: var(--any-value); - } - .rounded-l-\[var\(--any-value\)\] { - border-top-left-radius: var(--any-value); - border-bottom-left-radius: var(--any-value); - } - .rounded-r-\[var\(--any-value\)\] { - border-top-right-radius: var(--any-value); - border-bottom-right-radius: var(--any-value); - } - .rounded-t-\[var\(--any-value\)\] { - border-top-left-radius: var(--any-value); - border-top-right-radius: var(--any-value); - } - .rounded-bl-\[var\(--any-value\)\] { - border-bottom-left-radius: var(--any-value); - } - .rounded-br-\[var\(--any-value\)\] { - border-bottom-right-radius: var(--any-value); - } - .rounded-tl-\[var\(--any-value\)\] { - border-top-left-radius: var(--any-value); - } - .rounded-tr-\[var\(--any-value\)\] { - border-top-right-radius: var(--any-value); - } - .border-\[var\(--any-value\)\] { - border-color: var(--any-value); - } - .border-x-\[var\(--any-value\)\] { - border-left-color: var(--any-value); - border-right-color: var(--any-value); - } - .border-y-\[var\(--any-value\)\] { - border-top-color: var(--any-value); - border-bottom-color: var(--any-value); - } - .border-b-\[var\(--any-value\)\] { - border-bottom-color: var(--any-value); - } - .border-l-\[var\(--any-value\)\] { - border-left-color: var(--any-value); - } - .border-r-\[var\(--any-value\)\] { - border-right-color: var(--any-value); - } - .border-t-\[var\(--any-value\)\] { - border-top-color: var(--any-value); - } - .bg-\[var\(--any-value\)\] { - background-color: var(--any-value); - } - .from-\[var\(--any-value\)\] { - --tw-gradient-from: var(--any-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); - } - .via-\[var\(--any-value\)\] { - --tw-gradient-to: #fff0 var(--tw-gradient-to-position); - --tw-gradient-stops: var(--tw-gradient-from), - var(--any-value) var(--tw-gradient-via-position), var(--tw-gradient-to); - } - .to-\[var\(--any-value\)\] { - --tw-gradient-to: var(--any-value) var(--tw-gradient-to-position); - } - .fill-\[var\(--any-value\)\] { - fill: var(--any-value); - } - .stroke-\[var\(--any-value\)\] { - stroke: var(--any-value); - } - .object-\[var\(--any-value\)\] { - object-position: var(--any-value); - } - .p-\[var\(--any-value\)\] { - padding: var(--any-value); - } - .px-\[var\(--any-value\)\] { - padding-left: var(--any-value); - padding-right: var(--any-value); - } - .py-\[var\(--any-value\)\] { - padding-top: var(--any-value); - padding-bottom: var(--any-value); - } - .pb-\[var\(--any-value\)\] { - padding-bottom: var(--any-value); - } - .pl-\[var\(--any-value\)\] { - padding-left: var(--any-value); - } - .pr-\[var\(--any-value\)\] { - padding-right: var(--any-value); - } - .pt-\[var\(--any-value\)\] { - padding-top: var(--any-value); - } - .indent-\[var\(--any-value\)\] { - text-indent: var(--any-value); - } - .align-\[var\(--any-value\)\] { - vertical-align: var(--any-value); - } - .font-\[var\(--any-value\)\] { - font-weight: var(--any-value); - } - .leading-\[var\(--any-value\)\] { - line-height: var(--any-value); - } - .tracking-\[var\(--any-value\)\] { - letter-spacing: var(--any-value); - } - .text-\[var\(--any-value\)\] { - color: var(--any-value); - } - .decoration-\[var\(--any-value\)\] { - text-decoration-color: var(--any-value); - } - .underline-offset-\[var\(--any-value\)\] { - text-underline-offset: var(--any-value); - } - .placeholder-\[var\(--any-value\)\]::placeholder { - color: var(--any-value); - } - .caret-\[var\(--any-value\)\] { - caret-color: var(--any-value); - } - .accent-\[var\(--any-value\)\] { - accent-color: var(--any-value); - } - .opacity-\[var\(--any-value\)\] { - opacity: var(--any-value); - } - .shadow-\[var\(--any-value\)\] { - --tw-shadow-color: var(--any-value); - --tw-shadow: var(--tw-shadow-colored); - } - .outline-offset-\[var\(--any-value\)\] { - outline-offset: var(--any-value); - } - .outline-\[var\(--any-value\)\] { - outline-color: var(--any-value); - } - .ring-\[var\(--any-value\)\] { - --tw-ring-color: var(--any-value); - } - .ring-offset-\[var\(--any-value\)\] { - --tw-ring-offset-color: var(--any-value); - } - .blur-\[var\(--any-value\)\] { - --tw-blur: blur(var(--any-value)); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) - var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) - var(--tw-drop-shadow); - } - .brightness-\[var\(--any-value\)\] { - --tw-brightness: brightness(var(--any-value)); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) - var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) - var(--tw-drop-shadow); - } - .contrast-\[var\(--any-value\)\] { - --tw-contrast: contrast(var(--any-value)); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) - var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) - var(--tw-drop-shadow); - } - .drop-shadow-\[var\(--any-value\)\] { - --tw-drop-shadow: drop-shadow(var(--any-value)); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) - var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) - var(--tw-drop-shadow); - } - .grayscale-\[var\(--any-value\)\] { - --tw-grayscale: grayscale(var(--any-value)); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) - var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) - var(--tw-drop-shadow); - } - .hue-rotate-\[var\(--any-value\)\] { - --tw-hue-rotate: hue-rotate(var(--any-value)); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) - var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) - var(--tw-drop-shadow); - } - .invert-\[var\(--any-value\)\] { - --tw-invert: invert(var(--any-value)); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) - var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) - var(--tw-drop-shadow); - } - .saturate-\[var\(--any-value\)\] { - --tw-saturate: saturate(var(--any-value)); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) - var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) - var(--tw-drop-shadow); - } - .sepia-\[var\(--any-value\)\] { - --tw-sepia: sepia(var(--any-value)); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) - var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) - var(--tw-drop-shadow); - } - .backdrop-blur-\[var\(--any-value\)\] { - --tw-backdrop-blur: blur(var(--any-value)); - 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); - } - .backdrop-brightness-\[var\(--any-value\)\] { - --tw-backdrop-brightness: brightness(var(--any-value)); - 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); - } - .backdrop-contrast-\[var\(--any-value\)\] { - --tw-backdrop-contrast: contrast(var(--any-value)); - 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); - } - .backdrop-grayscale-\[var\(--any-value\)\] { - --tw-backdrop-grayscale: grayscale(var(--any-value)); - 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); - } - .backdrop-hue-rotate-\[var\(--any-value\)\] { - --tw-backdrop-hue-rotate: hue-rotate(var(--any-value)); - 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); - } - .backdrop-invert-\[var\(--any-value\)\] { - --tw-backdrop-invert: invert(var(--any-value)); - 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); - } - .backdrop-opacity-\[var\(--any-value\)\] { - --tw-backdrop-opacity: opacity(var(--any-value)); - 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); - } - .backdrop-saturate-\[var\(--any-value\)\] { - --tw-backdrop-saturate: saturate(var(--any-value)); - 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); - } - .backdrop-sepia-\[var\(--any-value\)\] { - --tw-backdrop-sepia: sepia(var(--any-value)); - 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); - } - .transition-\[var\(--any-value\)\] { - transition-property: var(--any-value); - transition-duration: 0.15s; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - } - .delay-\[var\(--any-value\)\] { - transition-delay: var(--any-value); - } - .duration-\[var\(--any-value\)\] { - transition-duration: var(--any-value); - } - .ease-\[var\(--any-value\)\] { - transition-timing-function: var(--any-value); - } - .will-change-\[var\(--any-value\)\] { - will-change: var(--any-value); - } - .content-\[var\(--any-value\)\] { - --tw-content: var(--any-value); - content: var(--tw-content); - } - `) - stable.expect(result.css).toMatchFormattedCss(css` - .inset-\[var\(--any-value\)\] { - inset: var(--any-value); - } - .inset-x-\[var\(--any-value\)\] { - left: var(--any-value); - right: var(--any-value); - } - .inset-y-\[var\(--any-value\)\] { - top: var(--any-value); - bottom: var(--any-value); - } - .bottom-\[var\(--any-value\)\] { - bottom: var(--any-value); - } - .left-\[var\(--any-value\)\] { - left: var(--any-value); - } - .right-\[var\(--any-value\)\] { - right: var(--any-value); - } - .top-\[var\(--any-value\)\] { - top: var(--any-value); - } - .z-\[var\(--any-value\)\] { - z-index: var(--any-value); - } - .order-\[var\(--any-value\)\] { - order: var(--any-value); - } - .col-\[var\(--any-value\)\] { - grid-column: var(--any-value); - } - .col-start-\[var\(--any-value\)\] { - grid-column-start: var(--any-value); - } - .col-end-\[var\(--any-value\)\] { - grid-column-end: var(--any-value); - } - .row-\[var\(--any-value\)\] { - grid-row: var(--any-value); - } - .row-start-\[var\(--any-value\)\] { - grid-row-start: var(--any-value); - } - .row-end-\[var\(--any-value\)\] { - grid-row-end: var(--any-value); - } - .m-\[var\(--any-value\)\] { - margin: var(--any-value); - } - .mx-\[var\(--any-value\)\] { - margin-left: var(--any-value); - margin-right: var(--any-value); - } - .my-\[var\(--any-value\)\] { - margin-top: var(--any-value); - margin-bottom: var(--any-value); - } - .mb-\[var\(--any-value\)\] { - margin-bottom: var(--any-value); - } - .ml-\[var\(--any-value\)\] { - margin-left: var(--any-value); - } - .mr-\[var\(--any-value\)\] { - margin-right: var(--any-value); - } - .mt-\[var\(--any-value\)\] { - margin-top: var(--any-value); - } - .aspect-\[var\(--any-value\)\] { - aspect-ratio: var(--any-value); - } - .h-\[var\(--any-value\)\] { - height: var(--any-value); - } - .max-h-\[var\(--any-value\)\] { - max-height: var(--any-value); - } - .min-h-\[var\(--any-value\)\] { - min-height: var(--any-value); - } - .w-\[var\(--any-value\)\] { - width: var(--any-value); - } - .min-w-\[var\(--any-value\)\] { - min-width: var(--any-value); - } - .max-w-\[var\(--any-value\)\] { - max-width: var(--any-value); - } - .flex-\[var\(--any-value\)\] { - flex: var(--any-value); - } - .flex-shrink-\[var\(--any-value\)\], - .shrink-\[var\(--any-value\)\] { - flex-shrink: var(--any-value); - } - .flex-grow-\[var\(--any-value\)\], - .grow-\[var\(--any-value\)\] { - flex-grow: var(--any-value); - } - .basis-\[var\(--any-value\)\] { - flex-basis: var(--any-value); - } - .border-spacing-\[var\(--any-value\)\] { - --tw-border-spacing-x: var(--any-value); - --tw-border-spacing-y: var(--any-value); - border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y); - } - .border-spacing-x-\[var\(--any-value\)\] { - --tw-border-spacing-x: var(--any-value); - border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y); - } - .border-spacing-y-\[var\(--any-value\)\] { - --tw-border-spacing-y: var(--any-value); - border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y); - } - .origin-\[var\(--any-value\)\] { - transform-origin: var(--any-value); - } - .translate-x-\[var\(--any-value\)\] { - --tw-translate-x: var(--any-value); - 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)); - } - .translate-y-\[var\(--any-value\)\] { - --tw-translate-y: var(--any-value); - 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)); - } - .rotate-\[var\(--any-value\)\] { - --tw-rotate: var(--any-value); - 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)); - } - .scale-\[var\(--any-value\)\] { - --tw-scale-x: var(--any-value); - --tw-scale-y: var(--any-value); - 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)); - } - .scale-x-\[var\(--any-value\)\] { - --tw-scale-x: var(--any-value); - 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)); - } - .scale-y-\[var\(--any-value\)\] { - --tw-scale-y: var(--any-value); - 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)); - } - .animate-\[var\(--any-value\)\] { - animation: var(--any-value); - } - .cursor-\[var\(--any-value\)\] { - cursor: var(--any-value); - } - .scroll-m-\[var\(--any-value\)\] { - scroll-margin: var(--any-value); - } - .scroll-mx-\[var\(--any-value\)\] { - scroll-margin-left: var(--any-value); - scroll-margin-right: var(--any-value); - } - .scroll-my-\[var\(--any-value\)\] { - scroll-margin-top: var(--any-value); - scroll-margin-bottom: var(--any-value); - } - .scroll-mb-\[var\(--any-value\)\] { - scroll-margin-bottom: var(--any-value); - } - .scroll-ml-\[var\(--any-value\)\] { - scroll-margin-left: var(--any-value); - } - .scroll-mr-\[var\(--any-value\)\] { - scroll-margin-right: var(--any-value); - } - .scroll-mt-\[var\(--any-value\)\] { - scroll-margin-top: var(--any-value); - } - .scroll-p-\[var\(--any-value\)\] { - scroll-padding: var(--any-value); - } - .scroll-px-\[var\(--any-value\)\] { - scroll-padding-left: var(--any-value); - scroll-padding-right: var(--any-value); - } - .scroll-py-\[var\(--any-value\)\] { - scroll-padding-top: var(--any-value); - scroll-padding-bottom: var(--any-value); - } - .scroll-pb-\[var\(--any-value\)\] { - scroll-padding-bottom: var(--any-value); - } - .scroll-pl-\[var\(--any-value\)\] { - scroll-padding-left: var(--any-value); - } - .scroll-pr-\[var\(--any-value\)\] { - scroll-padding-right: var(--any-value); - } - .scroll-pt-\[var\(--any-value\)\] { - scroll-padding-top: var(--any-value); - } - .list-\[var\(--any-value\)\] { - list-style-type: var(--any-value); - } - .columns-\[var\(--any-value\)\] { - columns: var(--any-value); - } - .auto-cols-\[var\(--any-value\)\] { - grid-auto-columns: var(--any-value); - } - .auto-rows-\[var\(--any-value\)\] { - grid-auto-rows: var(--any-value); - } - .grid-cols-\[var\(--any-value\)\] { - grid-template-columns: var(--any-value); - } - .grid-rows-\[var\(--any-value\)\] { - grid-template-rows: var(--any-value); - } - .gap-\[var\(--any-value\)\] { - gap: var(--any-value); - } - .gap-x-\[var\(--any-value\)\] { - column-gap: var(--any-value); - } - .gap-y-\[var\(--any-value\)\] { - row-gap: var(--any-value); - } - .space-x-\[var\(--any-value\)\] > :not([hidden]) ~ :not([hidden]) { - --tw-space-x-reverse: 0; - margin-right: calc(var(--any-value) * var(--tw-space-x-reverse)); - margin-left: calc(var(--any-value) * calc(1 - var(--tw-space-x-reverse))); - } - .space-y-\[var\(--any-value\)\] > :not([hidden]) ~ :not([hidden]) { - --tw-space-y-reverse: 0; - margin-top: calc(var(--any-value) * calc(1 - var(--tw-space-y-reverse))); - margin-bottom: calc(var(--any-value) * var(--tw-space-y-reverse)); - } - .divide-y-\[var\(--any-value\)\] > :not([hidden]) ~ :not([hidden]) { - --tw-divide-y-reverse: 0; - border-top-width: calc(var(--any-value) * calc(1 - var(--tw-divide-y-reverse))); - border-bottom-width: calc(var(--any-value) * var(--tw-divide-y-reverse)); - } - .divide-\[var\(--any-value\)\] > :not([hidden]) ~ :not([hidden]) { - border-color: var(--any-value); - } - .divide-opacity-\[var\(--any-value\)\] > :not([hidden]) ~ :not([hidden]) { - --tw-divide-opacity: var(--any-value); - } - .rounded-\[var\(--any-value\)\] { - border-radius: var(--any-value); - } - .rounded-b-\[var\(--any-value\)\] { - border-bottom-right-radius: var(--any-value); - border-bottom-left-radius: var(--any-value); - } - .rounded-l-\[var\(--any-value\)\] { - border-top-left-radius: var(--any-value); - border-bottom-left-radius: var(--any-value); - } - .rounded-r-\[var\(--any-value\)\] { - border-top-right-radius: var(--any-value); - border-bottom-right-radius: var(--any-value); - } - .rounded-t-\[var\(--any-value\)\] { - border-top-left-radius: var(--any-value); - border-top-right-radius: var(--any-value); - } - .rounded-bl-\[var\(--any-value\)\] { - border-bottom-left-radius: var(--any-value); - } - .rounded-br-\[var\(--any-value\)\] { - border-bottom-right-radius: var(--any-value); - } - .rounded-tl-\[var\(--any-value\)\] { - border-top-left-radius: var(--any-value); - } - .rounded-tr-\[var\(--any-value\)\] { - border-top-right-radius: var(--any-value); - } - .border-\[var\(--any-value\)\] { - border-color: var(--any-value); - } - .border-x-\[var\(--any-value\)\] { - border-left-color: var(--any-value); - border-right-color: var(--any-value); - } - .border-y-\[var\(--any-value\)\] { - border-top-color: var(--any-value); - border-bottom-color: var(--any-value); - } - .border-b-\[var\(--any-value\)\] { - border-bottom-color: var(--any-value); - } - .border-l-\[var\(--any-value\)\] { - border-left-color: var(--any-value); - } - .border-r-\[var\(--any-value\)\] { - border-right-color: var(--any-value); - } - .border-t-\[var\(--any-value\)\] { - border-top-color: var(--any-value); - } - .border-opacity-\[var\(--any-value\)\] { - --tw-border-opacity: var(--any-value); - } - .bg-\[var\(--any-value\)\] { - background-color: var(--any-value); - } - .bg-opacity-\[var\(--any-value\)\] { - --tw-bg-opacity: var(--any-value); - } - .from-\[var\(--any-value\)\] { - --tw-gradient-from: var(--any-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); - } - .via-\[var\(--any-value\)\] { - --tw-gradient-to: #fff0 var(--tw-gradient-to-position); - --tw-gradient-stops: var(--tw-gradient-from), - var(--any-value) var(--tw-gradient-via-position), var(--tw-gradient-to); - } - .to-\[var\(--any-value\)\] { - --tw-gradient-to: var(--any-value) var(--tw-gradient-to-position); - } - .fill-\[var\(--any-value\)\] { - fill: var(--any-value); - } - .stroke-\[var\(--any-value\)\] { - stroke: var(--any-value); - } - .object-\[var\(--any-value\)\] { - object-position: var(--any-value); - } - .p-\[var\(--any-value\)\] { - padding: var(--any-value); - } - .px-\[var\(--any-value\)\] { - padding-left: var(--any-value); - padding-right: var(--any-value); - } - .py-\[var\(--any-value\)\] { - padding-top: var(--any-value); - padding-bottom: var(--any-value); - } - .pb-\[var\(--any-value\)\] { - padding-bottom: var(--any-value); - } - .pl-\[var\(--any-value\)\] { - padding-left: var(--any-value); - } - .pr-\[var\(--any-value\)\] { - padding-right: var(--any-value); - } - .pt-\[var\(--any-value\)\] { - padding-top: var(--any-value); - } - .indent-\[var\(--any-value\)\] { - text-indent: var(--any-value); - } - .align-\[var\(--any-value\)\] { - vertical-align: var(--any-value); - } - .font-\[var\(--any-value\)\] { - font-weight: var(--any-value); - } - .leading-\[var\(--any-value\)\] { - line-height: var(--any-value); - } - .tracking-\[var\(--any-value\)\] { - letter-spacing: var(--any-value); - } - .text-\[var\(--any-value\)\] { - color: var(--any-value); - } - .text-opacity-\[var\(--any-value\)\] { - --tw-text-opacity: var(--any-value); - } - .decoration-\[var\(--any-value\)\] { - text-decoration-color: var(--any-value); - } - .underline-offset-\[var\(--any-value\)\] { - text-underline-offset: var(--any-value); - } - .placeholder-\[var\(--any-value\)\]::placeholder { - color: var(--any-value); - } - .placeholder-opacity-\[var\(--any-value\)\]::placeholder { - --tw-placeholder-opacity: var(--any-value); - } - .caret-\[var\(--any-value\)\] { - caret-color: var(--any-value); - } - .accent-\[var\(--any-value\)\] { - accent-color: var(--any-value); - } - .opacity-\[var\(--any-value\)\] { - opacity: var(--any-value); - } - .shadow-\[var\(--any-value\)\] { - --tw-shadow-color: var(--any-value); - --tw-shadow: var(--tw-shadow-colored); - } - .outline-offset-\[var\(--any-value\)\] { - outline-offset: var(--any-value); - } - .outline-\[var\(--any-value\)\] { - outline-color: var(--any-value); - } - .ring-\[var\(--any-value\)\] { - --tw-ring-color: var(--any-value); - } - .ring-opacity-\[var\(--any-value\)\] { - --tw-ring-opacity: var(--any-value); - } - .ring-offset-\[var\(--any-value\)\] { - --tw-ring-offset-color: var(--any-value); - } - .blur-\[var\(--any-value\)\] { - --tw-blur: blur(var(--any-value)); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) - var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) - var(--tw-drop-shadow); - } - .brightness-\[var\(--any-value\)\] { - --tw-brightness: brightness(var(--any-value)); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) - var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) - var(--tw-drop-shadow); - } - .contrast-\[var\(--any-value\)\] { - --tw-contrast: contrast(var(--any-value)); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) - var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) - var(--tw-drop-shadow); - } - .drop-shadow-\[var\(--any-value\)\] { - --tw-drop-shadow: drop-shadow(var(--any-value)); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) - var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) - var(--tw-drop-shadow); - } - .grayscale-\[var\(--any-value\)\] { - --tw-grayscale: grayscale(var(--any-value)); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) - var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) - var(--tw-drop-shadow); - } - .hue-rotate-\[var\(--any-value\)\] { - --tw-hue-rotate: hue-rotate(var(--any-value)); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) - var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) - var(--tw-drop-shadow); - } - .invert-\[var\(--any-value\)\] { - --tw-invert: invert(var(--any-value)); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) - var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) - var(--tw-drop-shadow); - } - .saturate-\[var\(--any-value\)\] { - --tw-saturate: saturate(var(--any-value)); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) - var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) - var(--tw-drop-shadow); - } - .sepia-\[var\(--any-value\)\] { - --tw-sepia: sepia(var(--any-value)); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) - var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) - var(--tw-drop-shadow); - } - .backdrop-blur-\[var\(--any-value\)\] { - --tw-backdrop-blur: blur(var(--any-value)); - 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); - } - .backdrop-brightness-\[var\(--any-value\)\] { - --tw-backdrop-brightness: brightness(var(--any-value)); - 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); - } - .backdrop-contrast-\[var\(--any-value\)\] { - --tw-backdrop-contrast: contrast(var(--any-value)); - 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); - } - .backdrop-grayscale-\[var\(--any-value\)\] { - --tw-backdrop-grayscale: grayscale(var(--any-value)); - 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); - } - .backdrop-hue-rotate-\[var\(--any-value\)\] { - --tw-backdrop-hue-rotate: hue-rotate(var(--any-value)); - 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); - } - .backdrop-invert-\[var\(--any-value\)\] { - --tw-backdrop-invert: invert(var(--any-value)); - 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); - } - .backdrop-opacity-\[var\(--any-value\)\] { - --tw-backdrop-opacity: opacity(var(--any-value)); - 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); - } - .backdrop-saturate-\[var\(--any-value\)\] { - --tw-backdrop-saturate: saturate(var(--any-value)); - 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); - } - .backdrop-sepia-\[var\(--any-value\)\] { - --tw-backdrop-sepia: sepia(var(--any-value)); - 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); - } - .transition-\[var\(--any-value\)\] { - transition-property: var(--any-value); - transition-duration: 0.15s; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - } - .delay-\[var\(--any-value\)\] { - transition-delay: var(--any-value); - } - .duration-\[var\(--any-value\)\] { - transition-duration: var(--any-value); - } - .ease-\[var\(--any-value\)\] { - transition-timing-function: var(--any-value); - } - .will-change-\[var\(--any-value\)\] { - will-change: var(--any-value); - } - .content-\[var\(--any-value\)\] { - --tw-content: var(--any-value); - content: var(--tw-content); - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .inset-\[var\(--any-value\)\] { + inset: var(--any-value); + } + .inset-x-\[var\(--any-value\)\] { + left: var(--any-value); + right: var(--any-value); + } + .inset-y-\[var\(--any-value\)\] { + top: var(--any-value); + bottom: var(--any-value); + } + .bottom-\[var\(--any-value\)\] { + bottom: var(--any-value); + } + .left-\[var\(--any-value\)\] { + left: var(--any-value); + } + .right-\[var\(--any-value\)\] { + right: var(--any-value); + } + .top-\[var\(--any-value\)\] { + top: var(--any-value); + } + .z-\[var\(--any-value\)\] { + z-index: var(--any-value); + } + .order-\[var\(--any-value\)\] { + order: var(--any-value); + } + .col-\[var\(--any-value\)\] { + grid-column: var(--any-value); + } + .col-start-\[var\(--any-value\)\] { + grid-column-start: var(--any-value); + } + .col-end-\[var\(--any-value\)\] { + grid-column-end: var(--any-value); + } + .row-\[var\(--any-value\)\] { + grid-row: var(--any-value); + } + .row-start-\[var\(--any-value\)\] { + grid-row-start: var(--any-value); + } + .row-end-\[var\(--any-value\)\] { + grid-row-end: var(--any-value); + } + .m-\[var\(--any-value\)\] { + margin: var(--any-value); + } + .mx-\[var\(--any-value\)\] { + margin-left: var(--any-value); + margin-right: var(--any-value); + } + .my-\[var\(--any-value\)\] { + margin-top: var(--any-value); + margin-bottom: var(--any-value); + } + .mb-\[var\(--any-value\)\] { + margin-bottom: var(--any-value); + } + .ml-\[var\(--any-value\)\] { + margin-left: var(--any-value); + } + .mr-\[var\(--any-value\)\] { + margin-right: var(--any-value); + } + .mt-\[var\(--any-value\)\] { + margin-top: var(--any-value); + } + .aspect-\[var\(--any-value\)\] { + aspect-ratio: var(--any-value); + } + .h-\[var\(--any-value\)\] { + height: var(--any-value); + } + .max-h-\[var\(--any-value\)\] { + max-height: var(--any-value); + } + .min-h-\[var\(--any-value\)\] { + min-height: var(--any-value); + } + .w-\[var\(--any-value\)\] { + width: var(--any-value); + } + .min-w-\[var\(--any-value\)\] { + min-width: var(--any-value); + } + .max-w-\[var\(--any-value\)\] { + max-width: var(--any-value); + } + .flex-\[var\(--any-value\)\] { + flex: var(--any-value); + } + .flex-shrink-\[var\(--any-value\)\], + .shrink-\[var\(--any-value\)\] { + flex-shrink: var(--any-value); + } + .flex-grow-\[var\(--any-value\)\], + .grow-\[var\(--any-value\)\] { + flex-grow: var(--any-value); + } + .basis-\[var\(--any-value\)\] { + flex-basis: var(--any-value); + } + .border-spacing-\[var\(--any-value\)\] { + --tw-border-spacing-x: var(--any-value); + --tw-border-spacing-y: var(--any-value); + border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y); + } + .border-spacing-x-\[var\(--any-value\)\] { + --tw-border-spacing-x: var(--any-value); + border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y); + } + .border-spacing-y-\[var\(--any-value\)\] { + --tw-border-spacing-y: var(--any-value); + border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y); + } + .origin-\[var\(--any-value\)\] { + transform-origin: var(--any-value); + } + .translate-x-\[var\(--any-value\)\] { + --tw-translate-x: var(--any-value); + 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)); + } + .translate-y-\[var\(--any-value\)\] { + --tw-translate-y: var(--any-value); + 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)); + } + .rotate-\[var\(--any-value\)\] { + --tw-rotate: var(--any-value); + 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)); + } + .scale-\[var\(--any-value\)\] { + --tw-scale-x: var(--any-value); + --tw-scale-y: var(--any-value); + 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)); + } + .scale-x-\[var\(--any-value\)\] { + --tw-scale-x: var(--any-value); + 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)); + } + .scale-y-\[var\(--any-value\)\] { + --tw-scale-y: var(--any-value); + 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)); + } + .animate-\[var\(--any-value\)\] { + animation: var(--any-value); + } + .cursor-\[var\(--any-value\)\] { + cursor: var(--any-value); + } + .scroll-m-\[var\(--any-value\)\] { + scroll-margin: var(--any-value); + } + .scroll-mx-\[var\(--any-value\)\] { + scroll-margin-left: var(--any-value); + scroll-margin-right: var(--any-value); + } + .scroll-my-\[var\(--any-value\)\] { + scroll-margin-top: var(--any-value); + scroll-margin-bottom: var(--any-value); + } + .scroll-mb-\[var\(--any-value\)\] { + scroll-margin-bottom: var(--any-value); + } + .scroll-ml-\[var\(--any-value\)\] { + scroll-margin-left: var(--any-value); + } + .scroll-mr-\[var\(--any-value\)\] { + scroll-margin-right: var(--any-value); + } + .scroll-mt-\[var\(--any-value\)\] { + scroll-margin-top: var(--any-value); + } + .scroll-p-\[var\(--any-value\)\] { + scroll-padding: var(--any-value); + } + .scroll-px-\[var\(--any-value\)\] { + scroll-padding-left: var(--any-value); + scroll-padding-right: var(--any-value); + } + .scroll-py-\[var\(--any-value\)\] { + scroll-padding-top: var(--any-value); + scroll-padding-bottom: var(--any-value); + } + .scroll-pb-\[var\(--any-value\)\] { + scroll-padding-bottom: var(--any-value); + } + .scroll-pl-\[var\(--any-value\)\] { + scroll-padding-left: var(--any-value); + } + .scroll-pr-\[var\(--any-value\)\] { + scroll-padding-right: var(--any-value); + } + .scroll-pt-\[var\(--any-value\)\] { + scroll-padding-top: var(--any-value); + } + .list-\[var\(--any-value\)\] { + list-style-type: var(--any-value); + } + .columns-\[var\(--any-value\)\] { + columns: var(--any-value); + } + .auto-cols-\[var\(--any-value\)\] { + grid-auto-columns: var(--any-value); + } + .auto-rows-\[var\(--any-value\)\] { + grid-auto-rows: var(--any-value); + } + .grid-cols-\[var\(--any-value\)\] { + grid-template-columns: var(--any-value); + } + .grid-rows-\[var\(--any-value\)\] { + grid-template-rows: var(--any-value); + } + .gap-\[var\(--any-value\)\] { + gap: var(--any-value); + } + .gap-x-\[var\(--any-value\)\] { + column-gap: var(--any-value); + } + .gap-y-\[var\(--any-value\)\] { + row-gap: var(--any-value); + } + .space-x-\[var\(--any-value\)\] > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(var(--any-value) * var(--tw-space-x-reverse)); + margin-left: calc(var(--any-value) * calc(1 - var(--tw-space-x-reverse))); + } + .space-y-\[var\(--any-value\)\] > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(var(--any-value) * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(var(--any-value) * var(--tw-space-y-reverse)); + } + .divide-y-\[var\(--any-value\)\] > :not([hidden]) ~ :not([hidden]) { + --tw-divide-y-reverse: 0; + border-top-width: calc(var(--any-value) * calc(1 - var(--tw-divide-y-reverse))); + border-bottom-width: calc(var(--any-value) * var(--tw-divide-y-reverse)); + } + .divide-\[var\(--any-value\)\] > :not([hidden]) ~ :not([hidden]) { + border-color: var(--any-value); + } + .divide-opacity-\[var\(--any-value\)\] > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: var(--any-value); + } + .rounded-\[var\(--any-value\)\] { + border-radius: var(--any-value); + } + .rounded-b-\[var\(--any-value\)\] { + border-bottom-right-radius: var(--any-value); + border-bottom-left-radius: var(--any-value); + } + .rounded-l-\[var\(--any-value\)\] { + border-top-left-radius: var(--any-value); + border-bottom-left-radius: var(--any-value); + } + .rounded-r-\[var\(--any-value\)\] { + border-top-right-radius: var(--any-value); + border-bottom-right-radius: var(--any-value); + } + .rounded-t-\[var\(--any-value\)\] { + border-top-left-radius: var(--any-value); + border-top-right-radius: var(--any-value); + } + .rounded-bl-\[var\(--any-value\)\] { + border-bottom-left-radius: var(--any-value); + } + .rounded-br-\[var\(--any-value\)\] { + border-bottom-right-radius: var(--any-value); + } + .rounded-tl-\[var\(--any-value\)\] { + border-top-left-radius: var(--any-value); + } + .rounded-tr-\[var\(--any-value\)\] { + border-top-right-radius: var(--any-value); + } + .border-\[var\(--any-value\)\] { + border-color: var(--any-value); + } + .border-x-\[var\(--any-value\)\] { + border-left-color: var(--any-value); + border-right-color: var(--any-value); + } + .border-y-\[var\(--any-value\)\] { + border-top-color: var(--any-value); + border-bottom-color: var(--any-value); + } + .border-b-\[var\(--any-value\)\] { + border-bottom-color: var(--any-value); + } + .border-l-\[var\(--any-value\)\] { + border-left-color: var(--any-value); + } + .border-r-\[var\(--any-value\)\] { + border-right-color: var(--any-value); + } + .border-t-\[var\(--any-value\)\] { + border-top-color: var(--any-value); + } + .border-opacity-\[var\(--any-value\)\] { + --tw-border-opacity: var(--any-value); + } + .bg-\[var\(--any-value\)\] { + background-color: var(--any-value); + } + .bg-opacity-\[var\(--any-value\)\] { + --tw-bg-opacity: var(--any-value); + } + .from-\[var\(--any-value\)\] { + --tw-gradient-from: var(--any-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); + } + .via-\[var\(--any-value\)\] { + --tw-gradient-to: #fff0 var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), + var(--any-value) var(--tw-gradient-via-position), var(--tw-gradient-to); + } + .to-\[var\(--any-value\)\] { + --tw-gradient-to: var(--any-value) var(--tw-gradient-to-position); + } + .fill-\[var\(--any-value\)\] { + fill: var(--any-value); + } + .stroke-\[var\(--any-value\)\] { + stroke: var(--any-value); + } + .object-\[var\(--any-value\)\] { + object-position: var(--any-value); + } + .p-\[var\(--any-value\)\] { + padding: var(--any-value); + } + .px-\[var\(--any-value\)\] { + padding-left: var(--any-value); + padding-right: var(--any-value); + } + .py-\[var\(--any-value\)\] { + padding-top: var(--any-value); + padding-bottom: var(--any-value); + } + .pb-\[var\(--any-value\)\] { + padding-bottom: var(--any-value); + } + .pl-\[var\(--any-value\)\] { + padding-left: var(--any-value); + } + .pr-\[var\(--any-value\)\] { + padding-right: var(--any-value); + } + .pt-\[var\(--any-value\)\] { + padding-top: var(--any-value); + } + .indent-\[var\(--any-value\)\] { + text-indent: var(--any-value); + } + .align-\[var\(--any-value\)\] { + vertical-align: var(--any-value); + } + .font-\[var\(--any-value\)\] { + font-weight: var(--any-value); + } + .leading-\[var\(--any-value\)\] { + line-height: var(--any-value); + } + .tracking-\[var\(--any-value\)\] { + letter-spacing: var(--any-value); + } + .text-\[var\(--any-value\)\] { + color: var(--any-value); + } + .text-opacity-\[var\(--any-value\)\] { + --tw-text-opacity: var(--any-value); + } + .decoration-\[var\(--any-value\)\] { + text-decoration-color: var(--any-value); + } + .underline-offset-\[var\(--any-value\)\] { + text-underline-offset: var(--any-value); + } + .placeholder-\[var\(--any-value\)\]::placeholder { + color: var(--any-value); + } + .placeholder-opacity-\[var\(--any-value\)\]::placeholder { + --tw-placeholder-opacity: var(--any-value); + } + .caret-\[var\(--any-value\)\] { + caret-color: var(--any-value); + } + .accent-\[var\(--any-value\)\] { + accent-color: var(--any-value); + } + .opacity-\[var\(--any-value\)\] { + opacity: var(--any-value); + } + .shadow-\[var\(--any-value\)\] { + --tw-shadow-color: var(--any-value); + --tw-shadow: var(--tw-shadow-colored); + } + .outline-offset-\[var\(--any-value\)\] { + outline-offset: var(--any-value); + } + .outline-\[var\(--any-value\)\] { + outline-color: var(--any-value); + } + .ring-\[var\(--any-value\)\] { + --tw-ring-color: var(--any-value); + } + .ring-opacity-\[var\(--any-value\)\] { + --tw-ring-opacity: var(--any-value); + } + .ring-offset-\[var\(--any-value\)\] { + --tw-ring-offset-color: var(--any-value); + } + .blur-\[var\(--any-value\)\] { + --tw-blur: blur(var(--any-value)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) + var(--tw-drop-shadow); + } + .brightness-\[var\(--any-value\)\] { + --tw-brightness: brightness(var(--any-value)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) + var(--tw-drop-shadow); + } + .contrast-\[var\(--any-value\)\] { + --tw-contrast: contrast(var(--any-value)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) + var(--tw-drop-shadow); + } + .drop-shadow-\[var\(--any-value\)\] { + --tw-drop-shadow: drop-shadow(var(--any-value)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) + var(--tw-drop-shadow); + } + .grayscale-\[var\(--any-value\)\] { + --tw-grayscale: grayscale(var(--any-value)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) + var(--tw-drop-shadow); + } + .hue-rotate-\[var\(--any-value\)\] { + --tw-hue-rotate: hue-rotate(var(--any-value)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) + var(--tw-drop-shadow); + } + .invert-\[var\(--any-value\)\] { + --tw-invert: invert(var(--any-value)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) + var(--tw-drop-shadow); + } + .saturate-\[var\(--any-value\)\] { + --tw-saturate: saturate(var(--any-value)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) + var(--tw-drop-shadow); + } + .sepia-\[var\(--any-value\)\] { + --tw-sepia: sepia(var(--any-value)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) + var(--tw-drop-shadow); + } + .backdrop-blur-\[var\(--any-value\)\] { + --tw-backdrop-blur: blur(var(--any-value)); + 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); + } + .backdrop-brightness-\[var\(--any-value\)\] { + --tw-backdrop-brightness: brightness(var(--any-value)); + 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); + } + .backdrop-contrast-\[var\(--any-value\)\] { + --tw-backdrop-contrast: contrast(var(--any-value)); + 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); + } + .backdrop-grayscale-\[var\(--any-value\)\] { + --tw-backdrop-grayscale: grayscale(var(--any-value)); + 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); + } + .backdrop-hue-rotate-\[var\(--any-value\)\] { + --tw-backdrop-hue-rotate: hue-rotate(var(--any-value)); + 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); + } + .backdrop-invert-\[var\(--any-value\)\] { + --tw-backdrop-invert: invert(var(--any-value)); + 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); + } + .backdrop-opacity-\[var\(--any-value\)\] { + --tw-backdrop-opacity: opacity(var(--any-value)); + 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); + } + .backdrop-saturate-\[var\(--any-value\)\] { + --tw-backdrop-saturate: saturate(var(--any-value)); + 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); + } + .backdrop-sepia-\[var\(--any-value\)\] { + --tw-backdrop-sepia: sepia(var(--any-value)); + 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); + } + .transition-\[var\(--any-value\)\] { + transition-property: var(--any-value); + transition-duration: 0.15s; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + } + .delay-\[var\(--any-value\)\] { + transition-delay: var(--any-value); + } + .duration-\[var\(--any-value\)\] { + transition-duration: var(--any-value); + } + .ease-\[var\(--any-value\)\] { + transition-timing-function: var(--any-value); + } + .will-change-\[var\(--any-value\)\] { + will-change: var(--any-value); + } + .content-\[var\(--any-value\)\] { + --tw-content: var(--any-value); + content: var(--tw-content); + } + `) }) - test.todo('rewrite the any test to be easier to understand or break it up into multiple tests') }) +test.todo('rewrite the any test to be easier to understand or break it up into multiple tests') diff --git a/tests/apply.test.js b/tests/apply.test.js index ca5416e9261a..f2f7253b3409 100644 --- a/tests/apply.test.js +++ b/tests/apply.test.js @@ -1,2525 +1,2145 @@ -import { crosscheck, run, html, css, defaults } from './util/run' - -crosscheck(({ stable, oxide }) => { - let sharedHtml = html` -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ` - - test('@apply', () => { - let config = { - darkMode: 'selector', - content: [{ raw: sharedHtml }], - } - - let input = css` - @tailwind components; - @tailwind utilities; - - @layer components { - .basic-example { - @apply rounded-md bg-blue-500 px-4 py-2; - } - .class-order { - @apply p-8 px-3 py-7 pt-4 pr-1; - } - .with-additional-properties { - font-weight: 500; - @apply text-right; - } - .variants { - @apply font-semibold hover:font-bold focus:font-medium lg:font-light xl:focus:font-black; - } - .only-variants { - @apply hover:font-bold focus:font-medium lg:font-light xl:focus:font-black; - } - .apply-group-variant { - @apply group-hover:text-center lg:group-hover:text-left; - } - .apply-dark-variant { - @apply dark:text-center dark:hover:text-right lg:dark:text-left; - } - .apply-custom-utility { - @apply custom-util hover:custom-util lg:custom-util xl:focus:custom-util; - } - .multiple, - .selectors { - @apply rounded-md bg-blue-500 px-4 py-2; - } - .multiple-variants, - .selectors-variants { - @apply hover:text-center active:text-right lg:focus:text-left; - } - .multiple-group, - .selectors-group { - @apply group-hover:text-center lg:group-hover:text-left; - } - .complex-utilities { - @apply ordinal tabular-nums shadow-lg hover:shadow-xl focus:diagonal-fractions; - } - .use-base-only-a { - @apply font-bold; - } - .use-base-only-b { - @apply use-base-only-a font-normal; - } - .use-dependant-only-a { - @apply font-bold; - } - .use-dependant-only-b { - @apply use-dependant-only-a font-normal; - } - .btn { - @apply rounded py-2 px-4 font-bold; - } - .btn-blue { - @apply btn bg-blue-500 text-white hover:bg-blue-700; - } - .recursive-apply-a { - @apply font-black sm:font-thin; - } - .recursive-apply-b { - @apply recursive-apply-a font-semibold md:font-extralight; - } - .recursive-apply-c { - @apply recursive-apply-b font-bold lg:font-light; - } - .use-with-other-properties-base { - color: green; - @apply font-bold; - } - .use-with-other-properties-component { - @apply use-with-other-properties-base; - } - .add-sibling-properties { - padding: 2rem; - @apply px-4 hover:px-2 lg:px-10 xl:focus:px-1; - padding-top: 3px; - @apply use-with-other-properties-base; - } +import { run, html, css, defaults } from './util/run' + +let sharedHtml = html` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+` + +test('@apply', () => { + let config = { + darkMode: 'selector', + content: [{ raw: sharedHtml }], + } + + let input = css` + @tailwind components; + @tailwind utilities; + + @layer components { + .basic-example { + @apply rounded-md bg-blue-500 px-4 py-2; + } + .class-order { + @apply p-8 px-3 py-7 pt-4 pr-1; + } + .with-additional-properties { + font-weight: 500; + @apply text-right; + } + .variants { + @apply font-semibold hover:font-bold focus:font-medium lg:font-light xl:focus:font-black; + } + .only-variants { + @apply hover:font-bold focus:font-medium lg:font-light xl:focus:font-black; + } + .apply-group-variant { + @apply group-hover:text-center lg:group-hover:text-left; + } + .apply-dark-variant { + @apply dark:text-center dark:hover:text-right lg:dark:text-left; + } + .apply-custom-utility { + @apply custom-util hover:custom-util lg:custom-util xl:focus:custom-util; + } + .multiple, + .selectors { + @apply rounded-md bg-blue-500 px-4 py-2; + } + .multiple-variants, + .selectors-variants { + @apply hover:text-center active:text-right lg:focus:text-left; + } + .multiple-group, + .selectors-group { + @apply group-hover:text-center lg:group-hover:text-left; + } + .complex-utilities { + @apply ordinal tabular-nums shadow-lg hover:shadow-xl focus:diagonal-fractions; + } + .use-base-only-a { + @apply font-bold; + } + .use-base-only-b { + @apply use-base-only-a font-normal; + } + .use-dependant-only-a { + @apply font-bold; + } + .use-dependant-only-b { + @apply use-dependant-only-a font-normal; + } + .btn { + @apply rounded py-2 px-4 font-bold; + } + .btn-blue { + @apply btn bg-blue-500 text-white hover:bg-blue-700; + } + .recursive-apply-a { + @apply font-black sm:font-thin; + } + .recursive-apply-b { + @apply recursive-apply-a font-semibold md:font-extralight; + } + .recursive-apply-c { + @apply recursive-apply-b font-bold lg:font-light; + } + .use-with-other-properties-base { + color: green; + @apply font-bold; + } + .use-with-other-properties-component { + @apply use-with-other-properties-base; + } + .add-sibling-properties { + padding: 2rem; + @apply px-4 hover:px-2 lg:px-10 xl:focus:px-1; + padding-top: 3px; + @apply use-with-other-properties-base; + } - h1 { - @apply text-2xl sm:text-3xl lg:text-2xl; - } - h2 { - @apply text-2xl; - @apply lg:text-2xl; - @apply sm:text-2xl; - } + h1 { + @apply text-2xl sm:text-3xl lg:text-2xl; + } + h2 { + @apply text-2xl; + @apply lg:text-2xl; + @apply sm:text-2xl; + } - .important-modifier { - @apply !rounded-md px-4; - } + .important-modifier { + @apply !rounded-md px-4; + } - .important-modifier-variant { - @apply px-4 hover:!rounded-md; - } + .important-modifier-variant { + @apply px-4 hover:!rounded-md; } + } - @layer utilities { - .custom-util { - custom: stuff; - } + @layer utilities { + .custom-util { + custom: stuff; + } - .foo { - @apply animate-spin; - } + .foo { + @apply animate-spin; + } - .bar { - @apply animate-pulse !important; - } + .bar { + @apply animate-pulse !important; } - ` + } + ` - return run(input, config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .basic-example { - --tw-bg-opacity: 1; - background-color: rgb(59 130 246 / var(--tw-bg-opacity)); - border-radius: 0.375rem; - padding: 0.5rem 1rem; - } - .class-order { - padding: 1rem 0.25rem 1.75rem 0.75rem; - } - .with-additional-properties { - text-align: right; - font-weight: 500; - } + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .basic-example { + --tw-bg-opacity: 1; + background-color: rgb(59 130 246 / var(--tw-bg-opacity)); + border-radius: 0.375rem; + padding: 0.5rem 1rem; + } + .class-order { + padding: 1rem 0.25rem 1.75rem 0.75rem; + } + .with-additional-properties { + text-align: right; + font-weight: 500; + } + .variants { + font-weight: 600; + } + .variants:hover { + font-weight: 700; + } + .variants:focus { + font-weight: 500; + } + @media (min-width: 1024px) { .variants { - font-weight: 600; - } - .variants:hover { - font-weight: 700; + font-weight: 300; } + } + @media (min-width: 1280px) { .variants:focus { - font-weight: 500; - } - @media (min-width: 1024px) { - .variants { - font-weight: 300; - } - } - @media (min-width: 1280px) { - .variants:focus { - font-weight: 900; - } + font-weight: 900; } - .only-variants:hover { - font-weight: 700; + } + .only-variants:hover { + font-weight: 700; + } + .only-variants:focus { + font-weight: 500; + } + @media (min-width: 1024px) { + .only-variants { + font-weight: 300; } + } + @media (min-width: 1280px) { .only-variants:focus { - font-weight: 500; - } - @media (min-width: 1024px) { - .only-variants { - font-weight: 300; - } - } - @media (min-width: 1280px) { - .only-variants:focus { - font-weight: 900; - } + font-weight: 900; } + } + .group:hover .apply-group-variant { + text-align: center; + } + @media (min-width: 1024px) { .group:hover .apply-group-variant { - text-align: center; - } - @media (min-width: 1024px) { - .group:hover .apply-group-variant { - text-align: left; - } + text-align: left; } + } + .apply-dark-variant:where(.dark, .dark *) { + text-align: center; + } + .apply-dark-variant:hover:where(.dark, .dark *) { + text-align: right; + } + @media (min-width: 1024px) { .apply-dark-variant:where(.dark, .dark *) { - text-align: center; - } - .apply-dark-variant:hover:where(.dark, .dark *) { - text-align: right; - } - @media (min-width: 1024px) { - .apply-dark-variant:where(.dark, .dark *) { - text-align: left; - } + text-align: left; } - .apply-custom-utility, - .apply-custom-utility:hover { + } + .apply-custom-utility, + .apply-custom-utility:hover { + custom: stuff; + } + @media (min-width: 1024px) { + .apply-custom-utility { custom: stuff; } - @media (min-width: 1024px) { - .apply-custom-utility { - custom: stuff; - } - } - @media (min-width: 1280px) { - .apply-custom-utility:focus { - custom: stuff; - } - } - .multiple, - .selectors { - --tw-bg-opacity: 1; - background-color: rgb(59 130 246 / var(--tw-bg-opacity)); - border-radius: 0.375rem; - padding: 0.5rem 1rem; - } - .multiple-variants:hover, - .selectors-variants:hover { - text-align: center; - } - .multiple-variants:active, - .selectors-variants:active { - text-align: right; + } + @media (min-width: 1280px) { + .apply-custom-utility:focus { + custom: stuff; } - @media (min-width: 1024px) { - .multiple-variants:focus, - .selectors-variants:focus { - text-align: left; - } + } + .multiple, + .selectors { + --tw-bg-opacity: 1; + background-color: rgb(59 130 246 / var(--tw-bg-opacity)); + border-radius: 0.375rem; + padding: 0.5rem 1rem; + } + .multiple-variants:hover, + .selectors-variants:hover { + text-align: center; + } + .multiple-variants:active, + .selectors-variants:active { + text-align: right; + } + @media (min-width: 1024px) { + .multiple-variants:focus, + .selectors-variants:focus { + text-align: left; } + } + .group:hover .multiple-group, + .group:hover .selectors-group { + text-align: center; + } + @media (min-width: 1024px) { .group:hover .multiple-group, .group:hover .selectors-group { - text-align: center; - } - @media (min-width: 1024px) { - .group:hover .multiple-group, - .group:hover .selectors-group { - text-align: left; - } - } - .complex-utilities { - --tw-ordinal: ordinal; - --tw-numeric-spacing: tabular-nums; - font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) - var(--tw-numeric-spacing) var(--tw-numeric-fraction); - --tw-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a; - --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), - 0 4px 6px -4px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), - var(--tw-shadow); - } - .complex-utilities:hover { - --tw-shadow: 0 20px 25px -5px #0000001a, 0 8px 10px -6px #0000001a; - --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), - 0 8px 10px -6px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), - var(--tw-shadow); - } - .complex-utilities:focus { - --tw-numeric-fraction: diagonal-fractions; - font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) - var(--tw-numeric-spacing) var(--tw-numeric-fraction); - } - .use-base-only-a { - font-weight: 700; - } - .use-dependant-only-b { - font-weight: 400; - } - .btn { - border-radius: 0.25rem; - padding: 0.5rem 1rem; - font-weight: 700; - } - .btn-blue { - --tw-bg-opacity: 1; - background-color: rgb(59 130 246 / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: rgb(255 255 255 / var(--tw-text-opacity)); - border-radius: 0.25rem; - padding: 0.5rem 1rem; - font-weight: 700; - } - .btn-blue:hover { - --tw-bg-opacity: 1; - background-color: rgb(29 78 216 / var(--tw-bg-opacity)); - } + text-align: left; + } + } + .complex-utilities { + --tw-ordinal: ordinal; + --tw-numeric-spacing: tabular-nums; + font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) + var(--tw-numeric-spacing) var(--tw-numeric-fraction); + --tw-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a; + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), + 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); + } + .complex-utilities:hover { + --tw-shadow: 0 20px 25px -5px #0000001a, 0 8px 10px -6px #0000001a; + --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), + 0 8px 10px -6px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); + } + .complex-utilities:focus { + --tw-numeric-fraction: diagonal-fractions; + font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) + var(--tw-numeric-spacing) var(--tw-numeric-fraction); + } + .use-base-only-a { + font-weight: 700; + } + .use-dependant-only-b { + font-weight: 400; + } + .btn { + border-radius: 0.25rem; + padding: 0.5rem 1rem; + font-weight: 700; + } + .btn-blue { + --tw-bg-opacity: 1; + background-color: rgb(59 130 246 / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); + border-radius: 0.25rem; + padding: 0.5rem 1rem; + font-weight: 700; + } + .btn-blue:hover { + --tw-bg-opacity: 1; + background-color: rgb(29 78 216 / var(--tw-bg-opacity)); + } + .recursive-apply-a { + font-weight: 900; + } + @media (min-width: 640px) { .recursive-apply-a { - font-weight: 900; - } - @media (min-width: 640px) { - .recursive-apply-a { - font-weight: 100; - } + font-weight: 100; } + } + .recursive-apply-b { + font-weight: 900; + } + @media (min-width: 640px) { .recursive-apply-b { - font-weight: 900; - } - @media (min-width: 640px) { - .recursive-apply-b { - font-weight: 100; - } + font-weight: 100; } + } + .recursive-apply-b { + font-weight: 600; + } + @media (min-width: 768px) { .recursive-apply-b { - font-weight: 600; - } - @media (min-width: 768px) { - .recursive-apply-b { - font-weight: 200; - } + font-weight: 200; } + } + .recursive-apply-c { + font-weight: 900; + } + @media (min-width: 640px) { .recursive-apply-c { - font-weight: 900; - } - @media (min-width: 640px) { - .recursive-apply-c { - font-weight: 100; - } + font-weight: 100; } + } + .recursive-apply-c { + font-weight: 600; + } + @media (min-width: 768px) { .recursive-apply-c { - font-weight: 600; - } - @media (min-width: 768px) { - .recursive-apply-c { - font-weight: 200; - } + font-weight: 200; } + } + .recursive-apply-c { + font-weight: 700; + } + @media (min-width: 1024px) { .recursive-apply-c { - font-weight: 700; - } - @media (min-width: 1024px) { - .recursive-apply-c { - font-weight: 300; - } - } - .use-with-other-properties-base, - .use-with-other-properties-component { - color: green; - font-weight: 700; + font-weight: 300; } + } + .use-with-other-properties-base, + .use-with-other-properties-component { + color: green; + font-weight: 700; + } + .add-sibling-properties { + padding: 2rem 1rem; + } + .add-sibling-properties:hover { + padding-left: 0.5rem; + padding-right: 0.5rem; + } + @media (min-width: 1024px) { .add-sibling-properties { - padding: 2rem 1rem; + padding-left: 2.5rem; + padding-right: 2.5rem; } - .add-sibling-properties:hover { - padding-left: 0.5rem; - padding-right: 0.5rem; - } - @media (min-width: 1024px) { - .add-sibling-properties { - padding-left: 2.5rem; - padding-right: 2.5rem; - } - } - @media (min-width: 1280px) { - .add-sibling-properties:focus { - padding-left: 0.25rem; - padding-right: 0.25rem; - } + } + @media (min-width: 1280px) { + .add-sibling-properties:focus { + padding-left: 0.25rem; + padding-right: 0.25rem; } - .add-sibling-properties { - color: green; - padding-top: 3px; - font-weight: 700; + } + .add-sibling-properties { + color: green; + padding-top: 3px; + font-weight: 700; + } + h1 { + font-size: 1.5rem; + line-height: 2rem; + } + @media (min-width: 640px) { + h1 { + font-size: 1.875rem; + line-height: 2.25rem; } + } + @media (min-width: 1024px) { h1 { font-size: 1.5rem; line-height: 2rem; } - @media (min-width: 640px) { - h1 { - font-size: 1.875rem; - line-height: 2.25rem; - } - } - @media (min-width: 1024px) { - h1 { - font-size: 1.5rem; - line-height: 2rem; - } - } + } + h2 { + font-size: 1.5rem; + line-height: 2rem; + } + @media (min-width: 1024px) { h2 { font-size: 1.5rem; line-height: 2rem; } - @media (min-width: 1024px) { - h2 { - font-size: 1.5rem; - line-height: 2rem; - } - } - @media (min-width: 640px) { - h2 { - font-size: 1.5rem; - line-height: 2rem; - } - } - .important-modifier { - padding-left: 1rem; - padding-right: 1rem; - border-radius: 0.375rem !important; - } - .important-modifier-variant { - padding-left: 1rem; - padding-right: 1rem; - } - .important-modifier-variant:hover { - border-radius: 0.375rem !important; - } - @keyframes spin { - to { - transform: rotate(360deg); - } - } - .foo { - animation: 1s linear infinite spin; - } - @keyframes pulse { - 50% { - opacity: 0.5; - } - } - .bar { - animation: 2s cubic-bezier(0.4, 0, 0.6, 1) infinite pulse !important; - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .basic-example { - background-color: #3b82f6; - border-radius: 0.375rem; - padding: 0.5rem 1rem; - } - .class-order { - padding: 1rem 0.25rem 1.75rem 0.75rem; - } - .with-additional-properties { - text-align: right; - font-weight: 500; - } - .variants { - font-weight: 600; - } - .variants:hover { - font-weight: 700; - } - .variants:focus { - font-weight: 500; - } - @media (min-width: 1024px) { - .variants { - font-weight: 300; - } - } - @media (min-width: 1280px) { - .variants:focus { - font-weight: 900; - } - } - .only-variants:hover { - font-weight: 700; - } - .only-variants:focus { - font-weight: 500; - } - @media (min-width: 1024px) { - .only-variants { - font-weight: 300; - } - } - @media (min-width: 1280px) { - .only-variants:focus { - font-weight: 900; - } - } - .group:hover .apply-group-variant { - text-align: center; - } - @media (min-width: 1024px) { - .group:hover .apply-group-variant { - text-align: left; - } - } - .apply-dark-variant:where(.dark, .dark *) { - text-align: center; - } - .apply-dark-variant:hover:where(.dark, .dark *) { - text-align: right; - } - @media (min-width: 1024px) { - .apply-dark-variant:where(.dark, .dark *) { - text-align: left; - } - } - .apply-custom-utility, - .apply-custom-utility:hover { - custom: stuff; - } - @media (min-width: 1024px) { - .apply-custom-utility { - custom: stuff; - } - } - @media (min-width: 1280px) { - .apply-custom-utility:focus { - custom: stuff; - } - } - .multiple, - .selectors { - background-color: #3b82f6; - border-radius: 0.375rem; - padding: 0.5rem 1rem; - } - .multiple-variants:hover, - .selectors-variants:hover { - text-align: center; - } - .multiple-variants:active, - .selectors-variants:active { - text-align: right; - } - @media (min-width: 1024px) { - .multiple-variants:focus, - .selectors-variants:focus { - text-align: left; - } - } - .group:hover .multiple-group, - .group:hover .selectors-group { - text-align: center; - } - @media (min-width: 1024px) { - .group:hover .multiple-group, - .group:hover .selectors-group { - text-align: left; - } - } - .complex-utilities { - --tw-ordinal: ordinal; - --tw-numeric-spacing: tabular-nums; - font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) - var(--tw-numeric-spacing) var(--tw-numeric-fraction); - --tw-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a; - --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), - 0 4px 6px -4px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), - var(--tw-shadow); - } - .complex-utilities:hover { - --tw-shadow: 0 20px 25px -5px #0000001a, 0 8px 10px -6px #0000001a; - --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), - 0 8px 10px -6px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), - var(--tw-shadow); - } - .complex-utilities:focus { - --tw-numeric-fraction: diagonal-fractions; - font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) - var(--tw-numeric-spacing) var(--tw-numeric-fraction); - } - .use-base-only-a { - font-weight: 700; - } - .use-dependant-only-b { - font-weight: 400; - } - .btn { - border-radius: 0.25rem; - padding: 0.5rem 1rem; - font-weight: 700; - } - .btn-blue { - color: #fff; - background-color: #3b82f6; - border-radius: 0.25rem; - padding: 0.5rem 1rem; - font-weight: 700; - } - .btn-blue:hover { - background-color: #1d4ed8; - } - .recursive-apply-a { - font-weight: 900; - } - @media (min-width: 640px) { - .recursive-apply-a { - font-weight: 100; - } - } - .recursive-apply-b { - font-weight: 900; - } - @media (min-width: 640px) { - .recursive-apply-b { - font-weight: 100; - } - } - .recursive-apply-b { - font-weight: 600; - } - @media (min-width: 768px) { - .recursive-apply-b { - font-weight: 200; - } - } - .recursive-apply-c { - font-weight: 900; - } - @media (min-width: 640px) { - .recursive-apply-c { - font-weight: 100; - } - } - .recursive-apply-c { - font-weight: 600; - } - @media (min-width: 768px) { - .recursive-apply-c { - font-weight: 200; - } - } - .recursive-apply-c { - font-weight: 700; - } - @media (min-width: 1024px) { - .recursive-apply-c { - font-weight: 300; - } - } - .use-with-other-properties-base, - .use-with-other-properties-component { - color: green; - font-weight: 700; - } - .add-sibling-properties { - padding: 2rem 1rem; - } - .add-sibling-properties:hover { - padding-left: 0.5rem; - padding-right: 0.5rem; - } - @media (min-width: 1024px) { - .add-sibling-properties { - padding-left: 2.5rem; - padding-right: 2.5rem; - } - } - @media (min-width: 1280px) { - .add-sibling-properties:focus { - padding-left: 0.25rem; - padding-right: 0.25rem; - } - } - .add-sibling-properties { - color: green; - padding-top: 3px; - font-weight: 700; - } - h1 { - font-size: 1.5rem; - line-height: 2rem; - } - @media (min-width: 640px) { - h1 { - font-size: 1.875rem; - line-height: 2.25rem; - } - } - @media (min-width: 1024px) { - h1 { - font-size: 1.5rem; - line-height: 2rem; - } - } + } + @media (min-width: 640px) { h2 { font-size: 1.5rem; line-height: 2rem; } - @media (min-width: 1024px) { - h2 { - font-size: 1.5rem; - line-height: 2rem; - } - } - @media (min-width: 640px) { - h2 { - font-size: 1.5rem; - line-height: 2rem; - } - } - .important-modifier { - padding-left: 1rem; - padding-right: 1rem; - border-radius: 0.375rem !important; - } - .important-modifier-variant { - padding-left: 1rem; - padding-right: 1rem; - } - .important-modifier-variant:hover { - border-radius: 0.375rem !important; - } - @keyframes spin { - to { - transform: rotate(360deg); - } - } - .foo { - animation: 1s linear infinite spin; - } - @keyframes pulse { - 50% { - opacity: 0.5; - } + } + .important-modifier { + padding-left: 1rem; + padding-right: 1rem; + border-radius: 0.375rem !important; + } + .important-modifier-variant { + padding-left: 1rem; + padding-right: 1rem; + } + .important-modifier-variant:hover { + border-radius: 0.375rem !important; + } + @keyframes spin { + to { + transform: rotate(360deg); } - .bar { - animation: 2s cubic-bezier(0.4, 0, 0.6, 1) infinite pulse !important; + } + .foo { + animation: 1s linear infinite spin; + } + @keyframes pulse { + 50% { + opacity: 0.5; } - `) - }) + } + .bar { + animation: 2s cubic-bezier(0.4, 0, 0.6, 1) infinite pulse !important; + } + `) }) +}) - test('@apply error with unknown utility', async () => { - let config = { - darkMode: 'selector', - content: [{ raw: sharedHtml }], - } +test('@apply error with unknown utility', async () => { + let config = { + darkMode: 'selector', + content: [{ raw: sharedHtml }], + } - let input = css` - @tailwind components; - @tailwind utilities; + let input = css` + @tailwind components; + @tailwind utilities; - @layer components { - .foo { - @apply a-utility-that-does-not-exist; - } + @layer components { + .foo { + @apply a-utility-that-does-not-exist; } - ` + } + ` - await expect(run(input, config)).rejects.toThrowError('class does not exist') - }) + await expect(run(input, config)).rejects.toThrowError('class does not exist') +}) - test('@apply error with nested @screen', async () => { - let config = { - darkMode: 'selector', - content: [{ raw: sharedHtml }], - } +test('@apply error with nested @screen', async () => { + let config = { + darkMode: 'selector', + content: [{ raw: sharedHtml }], + } - let input = css` - @tailwind components; - @tailwind utilities; + let input = css` + @tailwind components; + @tailwind utilities; - @layer components { - .foo { - @screen md { - @apply text-black; - } + @layer components { + .foo { + @screen md { + @apply text-black; } } - ` + } + ` - await expect(run(input, config)).rejects.toThrowError( - '@apply is not supported within nested at-rules like @screen' - ) - }) + await expect(run(input, config)).rejects.toThrowError( + '@apply is not supported within nested at-rules like @screen' + ) +}) - test('@apply error with nested @anyatrulehere', async () => { - let config = { - darkMode: 'selector', - content: [{ raw: sharedHtml }], - } +test('@apply error with nested @anyatrulehere', async () => { + let config = { + darkMode: 'selector', + content: [{ raw: sharedHtml }], + } - let input = css` - @tailwind components; - @tailwind utilities; + let input = css` + @tailwind components; + @tailwind utilities; - @layer components { - .foo { - @genie { - @apply text-black; - } + @layer components { + .foo { + @genie { + @apply text-black; } } - ` + } + ` - await expect(run(input, config)).rejects.toThrowError( - '@apply is not supported within nested at-rules like @genie' - ) - }) + await expect(run(input, config)).rejects.toThrowError( + '@apply is not supported within nested at-rules like @genie' + ) +}) - test('@apply error when using .group utility', async () => { - let config = { - darkMode: 'selector', - content: [{ raw: '
' }], - } +test('@apply error when using .group utility', async () => { + let config = { + darkMode: 'selector', + content: [{ raw: '
' }], + } - let input = css` - @tailwind components; - @tailwind utilities; + let input = css` + @tailwind components; + @tailwind utilities; - @layer components { - .foo { - @apply group; - } + @layer components { + .foo { + @apply group; } - ` + } + ` - await expect(run(input, config)).rejects.toThrowError( - `@apply should not be used with the 'group' utility` - ) - }) + await expect(run(input, config)).rejects.toThrowError( + `@apply should not be used with the 'group' utility` + ) +}) - test('@apply error when using a prefixed .group utility', async () => { - let config = { - prefix: 'tw-', - darkMode: 'selector', - content: [{ raw: html`
` }], - } +test('@apply error when using a prefixed .group utility', async () => { + let config = { + prefix: 'tw-', + darkMode: 'selector', + content: [{ raw: html`
` }], + } - let input = css` - @tailwind components; - @tailwind utilities; + let input = css` + @tailwind components; + @tailwind utilities; - @layer components { - .foo { - @apply tw-group; - } + @layer components { + .foo { + @apply tw-group; } - ` + } + ` - await expect(run(input, config)).rejects.toThrowError( - `@apply should not be used with the 'tw-group' utility` - ) - }) + await expect(run(input, config)).rejects.toThrowError( + `@apply should not be used with the 'tw-group' utility` + ) +}) - test('@apply error when using .peer utility', async () => { - let config = { - darkMode: 'selector', - content: [{ raw: '
' }], - } +test('@apply error when using .peer utility', async () => { + let config = { + darkMode: 'selector', + content: [{ raw: '
' }], + } - let input = css` - @tailwind components; - @tailwind utilities; + let input = css` + @tailwind components; + @tailwind utilities; - @layer components { - .foo { - @apply peer; - } + @layer components { + .foo { + @apply peer; } - ` + } + ` - await expect(run(input, config)).rejects.toThrowError( - `@apply should not be used with the 'peer' utility` - ) - }) + await expect(run(input, config)).rejects.toThrowError( + `@apply should not be used with the 'peer' utility` + ) +}) - test('@apply error when using a prefixed .peer utility', async () => { - let config = { - prefix: 'tw-', - darkMode: 'selector', - content: [{ raw: html`
` }], - } +test('@apply error when using a prefixed .peer utility', async () => { + let config = { + prefix: 'tw-', + darkMode: 'selector', + content: [{ raw: html`
` }], + } - let input = css` - @tailwind components; - @tailwind utilities; + let input = css` + @tailwind components; + @tailwind utilities; - @layer components { - .foo { - @apply tw-peer; - } + @layer components { + .foo { + @apply tw-peer; } - ` + } + ` - await expect(run(input, config)).rejects.toThrowError( - `@apply should not be used with the 'tw-peer' utility` - ) - }) + await expect(run(input, config)).rejects.toThrowError( + `@apply should not be used with the 'tw-peer' utility` + ) +}) - test('@apply classes from outside a @layer', async () => { - let config = { - content: [{ raw: html`
` }], +test('@apply classes from outside a @layer', async () => { + let config = { + content: [{ raw: html`
` }], + } + + let input = css` + @tailwind components; + @tailwind utilities; + + .foo { + @apply font-bold; } - let input = css` - @tailwind components; - @tailwind utilities; + .bar { + @apply foo text-red-500 hover:text-green-500; + } + + .baz { + @apply bar underline; + } + .keep-me-even-though-I-am-not-used-in-content { + color: green; + } + ` + + await run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .font-bold, .foo { - @apply font-bold; + font-weight: 700; } - .bar { - @apply foo text-red-500 hover:text-green-500; + --tw-text-opacity: 1; + color: rgb(239 68 68 / var(--tw-text-opacity)); + font-weight: 700; + } + .bar:hover { + --tw-text-opacity: 1; + color: rgb(34 197 94 / var(--tw-text-opacity)); } - .baz { - @apply bar underline; + --tw-text-opacity: 1; + color: rgb(239 68 68 / var(--tw-text-opacity)); + font-weight: 700; + text-decoration-line: underline; + } + .baz:hover { + --tw-text-opacity: 1; + color: rgb(34 197 94 / var(--tw-text-opacity)); } - .keep-me-even-though-I-am-not-used-in-content { color: green; } - ` - - await run(input, config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .font-bold, - .foo { - font-weight: 700; - } - .bar { - --tw-text-opacity: 1; - color: rgb(239 68 68 / var(--tw-text-opacity)); - font-weight: 700; - } - .bar:hover { - --tw-text-opacity: 1; - color: rgb(34 197 94 / var(--tw-text-opacity)); - } - .baz { - --tw-text-opacity: 1; - color: rgb(239 68 68 / var(--tw-text-opacity)); - font-weight: 700; - text-decoration-line: underline; - } - .baz:hover { - --tw-text-opacity: 1; - color: rgb(34 197 94 / var(--tw-text-opacity)); - } - .keep-me-even-though-I-am-not-used-in-content { - color: green; - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .font-bold, - .foo { - font-weight: 700; - } - .bar { - color: #ef4444; - font-weight: 700; - } - .bar:hover { - color: #22c55e; - } - .baz { - color: #ef4444; - font-weight: 700; - text-decoration-line: underline; - } - .baz:hover { - color: #22c55e; - } - .keep-me-even-though-I-am-not-used-in-content { - color: green; - } - `) - }) + `) }) +}) - test('@applying classes from outside a @layer respects the source order', async () => { - let config = { - content: [{ raw: html`
` }], +test('@applying classes from outside a @layer respects the source order', async () => { + let config = { + content: [{ raw: html`
` }], + } + + let input = css` + .baz { + @apply bar underline; } - let input = css` - .baz { - @apply bar underline; - } + @tailwind components; - @tailwind components; + .keep-me-even-though-I-am-not-used-in-content { + color: green; + } - .keep-me-even-though-I-am-not-used-in-content { - color: green; - } + @tailwind utilities; - @tailwind utilities; + .foo { + @apply font-bold; + } - .foo { - @apply font-bold; - } + .bar { + @apply no-underline; + } + ` - .bar { - @apply no-underline; + await run(input, config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .baz { + text-decoration-line: none; } - ` - - await run(input, config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .baz { - text-decoration-line: none; - } + .container { + width: 100%; + } + @media (min-width: 640px) { .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; - } + max-width: 640px; } - @media (min-width: 1536px) { - .container { - max-width: 1536px; - } + } + @media (min-width: 768px) { + .container { + max-width: 768px; } - .keep-me-even-though-I-am-not-used-in-content { - color: green; + } + @media (min-width: 1024px) { + .container { + max-width: 1024px; } - .font-bold, - .foo { - font-weight: 700; + } + @media (min-width: 1280px) { + .container { + max-width: 1280px; } - .bar { - text-decoration-line: none; + } + @media (min-width: 1536px) { + .container { + max-width: 1536px; } - `) - }) + } + .keep-me-even-though-I-am-not-used-in-content { + color: green; + } + .font-bold, + .foo { + font-weight: 700; + } + .bar { + text-decoration-line: none; + } + `) }) +}) - it('should remove duplicate properties when using apply with similar properties', () => { - let config = { - content: [{ raw: 'foo' }], - } +it('should remove duplicate properties when using apply with similar properties', () => { + let config = { + content: [{ raw: 'foo' }], + } - let input = css` - @tailwind utilities; + let input = css` + @tailwind utilities; + + .foo { + @apply absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transform; + } + ` + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` .foo { - @apply absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transform; + --tw-translate-x: -50%; + --tw-translate-y: -50%; + 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)); + position: absolute; + top: 50%; + left: 50%; } - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .foo { - --tw-translate-x: -50%; - --tw-translate-y: -50%; - 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)); - position: absolute; - top: 50%; - left: 50%; - } - `) - }) + `) }) +}) - it('should apply all the definitions of a class', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], - } - - let input = css` - @tailwind components; - @tailwind utilities; +it('should apply all the definitions of a class', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + } - @layer utilities { - .aspect-w-1 { - position: relative; - } + let input = css` + @tailwind components; + @tailwind utilities; - .aspect-w-1 { - --tw-aspect-w: 1; - } + @layer utilities { + .aspect-w-1 { + position: relative; } - @layer components { - .foo { - @apply aspect-w-1; - } + .aspect-w-1 { + --tw-aspect-w: 1; } - ` - - return run(input, config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .foo { - --tw-aspect-w: 1; - position: relative; - } - `) - }) - }) - - it('should throw when trying to apply a direct circular dependency', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], } - let input = css` - @tailwind components; - @tailwind utilities; - - @layer components { - .foo:not(.text-red-500) { - @apply text-red-500; - } + @layer components { + .foo { + @apply aspect-w-1; } - ` - - return run(input, config).catch((err) => { - expect(err.reason).toBe( - 'You cannot `@apply` the `text-red-500` utility here because it creates a circular dependency.' - ) - }) - }) - - it('should throw when trying to apply an indirect circular dependency', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], } + ` - let input = css` - @tailwind components; - @tailwind utilities; - - @layer components { - .a { - @apply b; - } - - .b { - @apply c; - } - - .c { - @apply a; - } + return run(input, config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .foo { + --tw-aspect-w: 1; + position: relative; } - ` - - return run(input, config).catch((err) => { - expect(err.reason).toBe( - 'You cannot `@apply` the `a` utility here because it creates a circular dependency.' - ) - }) + `) }) +}) - it('should not throw when the selector is different (but contains the base partially)', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], - } +it('should throw when trying to apply a direct circular dependency', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + } - let input = css` - @tailwind components; - @tailwind utilities; + let input = css` + @tailwind components; + @tailwind utilities; - .focus\:bg-gray-500 { - @apply bg-gray-500; + @layer components { + .foo:not(.text-red-500) { + @apply text-red-500; } - ` + } + ` - return run(input, config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .bg-gray-500, - .focus\:bg-gray-500 { - --tw-bg-opacity: 1; - background-color: rgb(107 114 128 / var(--tw-bg-opacity)); - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .bg-gray-500, - .focus\:bg-gray-500 { - background-color: #6b7280; - } - `) - }) + return run(input, config).catch((err) => { + expect(err.reason).toBe( + 'You cannot `@apply` the `text-red-500` utility here because it creates a circular dependency.' + ) }) +}) - it('should throw when trying to apply an indirect circular dependency with a modifier (1)', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], - } +it('should throw when trying to apply an indirect circular dependency', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + } - let input = css` - @tailwind components; - @tailwind utilities; + let input = css` + @tailwind components; + @tailwind utilities; - @layer components { - .a { - @apply b; - } + @layer components { + .a { + @apply b; + } - .b { - @apply c; - } + .b { + @apply c; + } - .c { - @apply hover:a; - } + .c { + @apply a; } - ` + } + ` - return run(input, config).catch((err) => { - expect(err.reason).toBe( - 'You cannot `@apply` the `hover:a` utility here because it creates a circular dependency.' - ) - }) + return run(input, config).catch((err) => { + expect(err.reason).toBe( + 'You cannot `@apply` the `a` utility here because it creates a circular dependency.' + ) }) +}) - it('should throw when trying to apply an indirect circular dependency with a modifier (2)', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], - } - - let input = css` - @tailwind components; - @tailwind utilities; +it('should not throw when the selector is different (but contains the base partially)', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + } - @layer components { - .a { - @apply b; - } + let input = css` + @tailwind components; + @tailwind utilities; - .b { - @apply hover:c; - } + .focus\:bg-gray-500 { + @apply bg-gray-500; + } + ` - .c { - @apply a; - } + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .bg-gray-500, + .focus\:bg-gray-500 { + --tw-bg-opacity: 1; + background-color: rgb(107 114 128 / var(--tw-bg-opacity)); } - ` - - return run(input, config).catch((err) => { - expect(err.reason).toBe( - 'You cannot `@apply` the `a` utility here because it creates a circular dependency.' - ) - }) + `) }) +}) - it('should not throw when the circular dependency is part of a different selector (1)', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], - } - - let input = css` - @tailwind utilities; +it('should throw when trying to apply an indirect circular dependency with a modifier (1)', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + } - @layer utilities { - html.dark .a, - .b { - color: red; - } - } + let input = css` + @tailwind components; + @tailwind utilities; - html.dark .c { + @layer components { + .a { @apply b; } - ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - html.dark .c { - color: red; - } - `) - }) - }) + .b { + @apply c; + } - it('should not throw when the circular dependency is part of a different selector (2)', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], + .c { + @apply hover:a; + } } + ` + + return run(input, config).catch((err) => { + expect(err.reason).toBe( + 'You cannot `@apply` the `hover:a` utility here because it creates a circular dependency.' + ) + }) +}) - let input = css` - @tailwind utilities; +it('should throw when trying to apply an indirect circular dependency with a modifier (2)', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + } - @layer utilities { - html.dark .a, - .b { - color: red; - } + let input = css` + @tailwind components; + @tailwind utilities; + + @layer components { + .a { + @apply b; } - html.dark .c { - @apply hover:b; + .b { + @apply hover:c; } - ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - html.dark .c:hover { - color: red; - } - `) - }) + .c { + @apply a; + } + } + ` + + return run(input, config).catch((err) => { + expect(err.reason).toBe( + 'You cannot `@apply` the `a` utility here because it creates a circular dependency.' + ) }) +}) - it('should throw when the circular dependency is part of the same selector', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], - } +it('should not throw when the circular dependency is part of a different selector (1)', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + } - let input = css` - @tailwind utilities; + let input = css` + @tailwind utilities; - @layer utilities { - html.dark .a, - html.dark .b { - color: red; - } + @layer utilities { + html.dark .a, + .b { + color: red; } + } + html.dark .c { + @apply b; + } + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` html.dark .c { - @apply hover:b; + color: red; } - ` - - return run(input, config).catch((err) => { - expect(err.reason).toBe( - 'You cannot `@apply` the `hover:b` utility here because it creates a circular dependency.' - ) - }) + `) }) +}) - it('rules with vendor prefixes are still separate when optimizing defaults rules', () => { - let config = { - experimental: { optimizeUniversalDefaults: true }, - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } +it('should not throw when the circular dependency is part of a different selector (2)', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + } - let input = css` - @tailwind base; - @tailwind components; - @tailwind utilities; + let input = css` + @tailwind utilities; - @layer components { - input[type='range']::-moz-range-thumb { - @apply border; - } + @layer utilities { + html.dark .a, + .b { + color: red; } - ` + } - return run(input, config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - input[type='range']::-moz-range-thumb { - border-width: 1px; - } - .border { - border-width: 1px; - } - `) - }) + html.dark .c { + @apply hover:b; + } + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + html.dark .c:hover { + color: red; + } + `) }) +}) - it('should be possible to apply user css', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], - } +it('should throw when the circular dependency is part of the same selector', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + } - let input = css` - @tailwind components; - @tailwind utilities; + let input = css` + @tailwind utilities; - .foo { + @layer utilities { + html.dark .a, + html.dark .b { color: red; } + } - .bar { - @apply foo; + html.dark .c { + @apply hover:b; + } + ` + + return run(input, config).catch((err) => { + expect(err.reason).toBe( + 'You cannot `@apply` the `hover:b` utility here because it creates a circular dependency.' + ) + }) +}) + +it('rules with vendor prefixes are still separate when optimizing defaults rules', () => { + let config = { + experimental: { optimizeUniversalDefaults: true }, + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind base; + @tailwind components; + @tailwind utilities; + + @layer components { + input[type='range']::-moz-range-thumb { + @apply border; } - ` + } + ` - return run(input, config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .foo, - .bar { - color: red; - } - `) - }) + return run(input, config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + input[type='range']::-moz-range-thumb { + border-width: 1px; + } + .border { + border-width: 1px; + } + `) }) +}) - it('should not be possible to apply user css with variants', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], +it('should be possible to apply user css', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + } + + let input = css` + @tailwind components; + @tailwind utilities; + + .foo { + color: red; } - let input = css` - @tailwind components; - @tailwind utilities; + .bar { + @apply foo; + } + ` - .foo { + return run(input, config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .foo, + .bar { color: red; } + `) + }) +}) - .bar { - @apply hover:foo; - } - ` +it('should not be possible to apply user css with variants', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + } - return run(input, config).catch((err) => { - expect(err.reason).toBe( - 'The `hover:foo` class does not exist. If `hover:foo` is a custom class, make sure it is defined within a `@layer` directive.' - ) - }) - }) + let input = css` + @tailwind components; + @tailwind utilities; - it('should not apply unrelated siblings when applying something from within atrules', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], + .foo { + color: red; } - let input = css` - @tailwind components; - @tailwind utilities; + .bar { + @apply hover:foo; + } + ` - @layer components { - .foo { - font-weight: bold; - @apply bar; - } + return run(input, config).catch((err) => { + expect(err.reason).toBe( + 'The `hover:foo` class does not exist. If `hover:foo` is a custom class, make sure it is defined within a `@layer` directive.' + ) + }) +}) + +it('should not apply unrelated siblings when applying something from within atrules', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + } + + let input = css` + @tailwind components; + @tailwind utilities; + + @layer components { + .foo { + font-weight: bold; + @apply bar; + } + + .bar { + color: green; + } + @supports (a: b) { .bar { - color: green; + color: blue; } - @supports (a: b) { - .bar { - color: blue; - } - - .something-unrelated { - color: red; - } + .something-unrelated { + color: red; } } - ` + } + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo { + color: green; + font-weight: bold; + } + @supports (a: b) { .foo { - color: green; - font-weight: bold; - } - @supports (a: b) { - .foo { - color: #00f; - } + color: #00f; } + } + .bar { + color: green; + } + @supports (a: b) { .bar { - color: green; + color: #00f; } - @supports (a: b) { - .bar { - color: #00f; - } - .something-unrelated { - color: red; - } + .something-unrelated { + color: red; } - `) - }) + } + `) }) +}) - it('should be possible to apply user css without tailwind directives', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], +it('should be possible to apply user css without tailwind directives', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + } + + let input = css` + .bop { + color: red; + } + .bar { + background-color: blue; + } + .foo { + @apply bar bop absolute; } + ` - let input = css` + return run(input, config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` .bop { color: red; } .bar { - background-color: blue; + background-color: #00f; } .foo { - @apply bar bop absolute; + color: red; + background-color: #00f; + position: absolute; } - ` + `) + }) +}) - return run(input, config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .bop { - color: red; - } - .bar { - background-color: #00f; - } - .foo { - color: red; - background-color: #00f; - position: absolute; - } - `) - }) +it('should be possible to apply a class from another rule with multiple selectors (2 classes)', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + } + + let input = css` + @tailwind utilities; + @layer utilities { + .a, + .b { + @apply underline; + } + + .c { + @apply b; + } + } + ` + + return run(input, config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .c { + text-decoration-line: underline; + } + `) }) +}) - it('should be possible to apply a class from another rule with multiple selectors (2 classes)', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], +it('should be possible to apply a class from another rule with multiple selectors (1 class, 1 tag)', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + } + + let input = css` + @tailwind utilities; + + @layer utilities { + span, + .b { + @apply underline; + } + + .c { + @apply b; + } } + ` - let input = css` - @tailwind utilities; - @layer utilities { - .a, - .b { - @apply underline; - } + return run(input, config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + span, + .b, + .c { + text-decoration-line: underline; + } + `) + }) +}) - .c { - @apply b; - } +it('should be possible to apply a class from another rule with multiple selectors (1 class, 1 id)', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + } + + let input = css` + @tailwind utilities; + @layer utilities { + #a, + .b { + @apply underline; } - ` - return run(input, config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .c { - text-decoration-line: underline; - } - `) - }) + .c { + @apply b; + } + } + ` + + return run(input, config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + #a, + .b, + .c { + text-decoration-line: underline; + } + `) }) +}) - it('should be possible to apply a class from another rule with multiple selectors (1 class, 1 tag)', () => { +describe('multiple instances', () => { + it('should be possible to apply multiple "instances" of the same class', () => { let config = { - content: [{ raw: html`
` }], + content: [{ raw: html`` }], plugins: [], + corePlugins: { preflight: false }, } let input = css` - @tailwind utilities; + .a { + @apply b; + } - @layer utilities { - span, - .b { - @apply underline; - } + .b { + @apply uppercase; + } - .c { - @apply b; - } + .b { + color: red; } ` return run(input, config).then((result) => { return expect(result.css).toMatchFormattedCss(css` - span, - .b, - .c { - text-decoration-line: underline; + .a, + .b { + text-transform: uppercase; + color: red; } `) }) }) - it('should be possible to apply a class from another rule with multiple selectors (1 class, 1 id)', () => { + it('should be possible to apply a combination of multiple "instances" of the same class', () => { let config = { - content: [{ raw: html`
` }], + content: [{ raw: html`` }], plugins: [], + corePlugins: { preflight: false }, } let input = css` - @tailwind utilities; - @layer utilities { - #a, - .b { - @apply underline; - } - - .c { - @apply b; - } + .a { + @apply b; + } + + .b { + @apply uppercase; + color: red; } ` return run(input, config).then((result) => { return expect(result.css).toMatchFormattedCss(css` - #a, - .b, - .c { - text-decoration-line: underline; + .a, + .b { + text-transform: uppercase; + color: red; } `) }) }) - describe('multiple instances', () => { - it('should be possible to apply multiple "instances" of the same class', () => { - let config = { - content: [{ raw: html`` }], - plugins: [], - corePlugins: { preflight: false }, - } + it('should generate the same output, even if it was used in a @layer', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind components; - let input = css` + @layer components { .a { @apply b; } .b { @apply uppercase; - } - - .b { color: red; } - ` - - return run(input, config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .a, - .b { - text-transform: uppercase; - color: red; - } - `) - }) - }) - - it('should be possible to apply a combination of multiple "instances" of the same class', () => { - let config = { - content: [{ raw: html`` }], - plugins: [], - corePlugins: { preflight: false }, } + ` - let input = css` - .a { - @apply b; - } - + return run(input, config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .a, .b { - @apply uppercase; + text-transform: uppercase; color: red; } - ` - - return run(input, config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .a, - .b { - text-transform: uppercase; - color: red; - } - `) - }) - }) - - it('should generate the same output, even if it was used in a @layer', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind components; - - @layer components { - .a { - @apply b; - } - - .b { - @apply uppercase; - color: red; - } - } - ` - - return run(input, config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .a, - .b { - text-transform: uppercase; - color: red; - } - `) - }) - }) - - it('should be possible to apply a combination of multiple "instances" of the same class (defined in a layer)', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind components; - - @layer components { - .a { - color: red; - @apply b; - color: blue; - } - - .b { - @apply text-green-500; - text-decoration: underline; - } - } - ` - - return run(input, config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .a { - color: red; - --tw-text-opacity: 1; - color: rgb(34 197 94 / var(--tw-text-opacity)); - color: #00f; - text-decoration: underline; - } - .b { - --tw-text-opacity: 1; - color: rgb(34 197 94 / var(--tw-text-opacity)); - text-decoration: underline; - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .a { - color: red; - color: #22c55e; - color: #00f; - text-decoration: underline; - } - .b { - color: #22c55e; - text-decoration: underline; - } - `) - }) - }) - - it('should properly maintain the order', () => { - let config = { - content: [{ raw: html`` }], - plugins: [], - corePlugins: { preflight: false }, - } - - let input = css` - h2 { - @apply text-xl; - @apply lg:text-3xl; - @apply sm:text-2xl; - } - ` - - return run(input, config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - h2 { - font-size: 1.25rem; - line-height: 1.75rem; - } - @media (min-width: 1024px) { - h2 { - font-size: 1.875rem; - line-height: 2.25rem; - } - } - @media (min-width: 640px) { - h2 { - font-size: 1.5rem; - line-height: 2rem; - } - } - `) - }) + `) }) }) - it('apply can emit defaults in isolated environments without @tailwind directives', () => { + it('should be possible to apply a combination of multiple "instances" of the same class (defined in a layer)', () => { let config = { - experimental: { optimizeUniversalDefaults: true }, - - content: [{ raw: html`
` }], + content: [{ raw: html`
` }], + plugins: [], + corePlugins: { preflight: false }, } let input = css` - .foo { - @apply focus:rotate-90; + @tailwind components; + + @layer components { + .a { + color: red; + @apply b; + color: blue; + } + + .b { + @apply text-green-500; + text-decoration: underline; + } } ` return run(input, config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .foo:focus { - --tw-rotate: 90deg; - 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)); + expect(result.css).toMatchFormattedCss(css` + .a { + color: red; + --tw-text-opacity: 1; + color: rgb(34 197 94 / var(--tw-text-opacity)); + color: #00f; + text-decoration: underline; + } + .b { + --tw-text-opacity: 1; + color: rgb(34 197 94 / var(--tw-text-opacity)); + text-decoration: underline; } `) }) }) - it('apply does not emit defaults in isolated environments without optimizeUniversalDefaults', () => { + it('should properly maintain the order', () => { let config = { - experimental: { optimizeUniversalDefaults: false }, - content: [{ raw: html`
` }], + content: [{ raw: html`` }], + plugins: [], corePlugins: { preflight: false }, } let input = css` - @tailwind base; - - .foo { - @apply focus:rotate-90; + h2 { + @apply text-xl; + @apply lg:text-3xl; + @apply sm:text-2xl; } ` return run(input, config).then((result) => { return expect(result.css).toMatchFormattedCss(css` - ${defaults} - .foo:focus { - --tw-rotate: 90deg; - 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)); + h2 { + font-size: 1.25rem; + line-height: 1.75rem; + } + @media (min-width: 1024px) { + h2 { + font-size: 1.875rem; + line-height: 2.25rem; + } + } + @media (min-width: 640px) { + h2 { + font-size: 1.5rem; + line-height: 2rem; + } } `) }) }) +}) - it('should work outside of layer', async () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } +it('apply can emit defaults in isolated environments without @tailwind directives', () => { + let config = { + experimental: { optimizeUniversalDefaults: true }, - let input = css` - .input-text { - @apply bg-white; - background-color: red; - } - ` + content: [{ raw: html`
` }], + } - let result - result = await run(input, config) + let input = css` + .foo { + @apply focus:rotate-90; + } + ` - stable.expect(result.css).toMatchFormattedCss(css` - .input-text { - --tw-bg-opacity: 1; - background-color: rgb(255 255 255 / var(--tw-bg-opacity)); - background-color: red; - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .input-text { - background-color: red; + return run(input, config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .foo:focus { + --tw-rotate: 90deg; + 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)); } `) + }) +}) - result = await run(input, config) +it('apply does not emit defaults in isolated environments without optimizeUniversalDefaults', () => { + let config = { + experimental: { optimizeUniversalDefaults: false }, + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } - stable.expect(result.css).toMatchFormattedCss(css` - .input-text { - --tw-bg-opacity: 1; - background-color: rgb(255 255 255 / var(--tw-bg-opacity)); - background-color: red; - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .input-text { - background-color: red; + let input = css` + @tailwind base; + + .foo { + @apply focus:rotate-90; + } + ` + + return run(input, config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + ${defaults} + .foo:focus { + --tw-rotate: 90deg; + 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)); } `) }) +}) - it('should work in layer', async () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, +it('should work outside of layer', async () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } + + let input = css` + .input-text { + @apply bg-white; + background-color: red; } + ` - let input = css` - @tailwind components; - @layer components { - .input-text { - @apply bg-white; - background-color: red; - } - } - ` + let result + result = await run(input, config) - await run(input, config) - const result = await run(input, config) + expect(result.css).toMatchFormattedCss(css` + .input-text { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); + background-color: red; + } + `) - stable.expect(result.css).toMatchFormattedCss(css` - .input-text { - --tw-bg-opacity: 1; - background-color: rgb(255 255 255 / var(--tw-bg-opacity)); - background-color: red; - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` + result = await run(input, config) + + expect(result.css).toMatchFormattedCss(css` + .input-text { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); + background-color: red; + } + `) +}) + +it('should work in layer', async () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind components; + @layer components { .input-text { + @apply bg-white; background-color: red; } - `) - }) - - it('apply partitioning works with media queries', async () => { - let config = { - content: [{ raw: html`` }], - corePlugins: { preflight: false }, } + ` - let input = css` - @tailwind base; - @layer base { - html, - body { - @apply text-green-600; - font-size: 1rem; - } + await run(input, config) + const result = await run(input, config) - @media print { - html, - body { - @apply text-red-600; - font-size: 2rem; - } - } - } - ` + expect(result.css).toMatchFormattedCss(css` + .input-text { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); + background-color: red; + } + `) +}) - await run(input, config) - const result = await run(input, config) +it('apply partitioning works with media queries', async () => { + let config = { + content: [{ raw: html`` }], + corePlugins: { preflight: false }, + } - stable.expect(result.css).toMatchFormattedCss(css` - html, - body { - --tw-text-opacity: 1; - color: rgb(22 163 74 / var(--tw-text-opacity)); - font-size: 1rem; - } - @media print { - html, - body { - --tw-text-opacity: 1; - color: rgb(220 38 38 / var(--tw-text-opacity)); - font-size: 2rem; - } - } - ${defaults} - `) - oxide.expect(result.css).toMatchFormattedCss(css` + let input = css` + @tailwind base; + @layer base { html, body { - color: #16a34a; + @apply text-green-600; font-size: 1rem; } + @media print { html, body { - color: #dc2626; + @apply text-red-600; font-size: 2rem; } } - ${defaults} - `) - }) - - it('should be possible to use apply in plugins', async () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - plugins: [ - function ({ addComponents }) { - addComponents({ - '.a': { - color: 'red', - }, - '.b': { - '@apply a': {}, - color: 'blue', - }, - }) - }, - ], - } - - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .a { - color: red; - } - .b { - color: red; - color: #00f; - } - `) - }) - }) - - it('should apply using the updated user CSS when the source has changed', async () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], } + ` - let inputBefore = css` - .foo { - color: green; - } - - .bar { - @apply foo; - } - ` - - let inputAfter = css` - .foo { - color: red; - } + await run(input, config) + const result = await run(input, config) - .bar { - @apply foo; + expect(result.css).toMatchFormattedCss(css` + html, + body { + --tw-text-opacity: 1; + color: rgb(22 163 74 / var(--tw-text-opacity)); + font-size: 1rem; + } + @media print { + html, + body { + --tw-text-opacity: 1; + color: rgb(220 38 38 / var(--tw-text-opacity)); + font-size: 2rem; } - ` - - let result = await run(inputBefore, config) + } + ${defaults} + `) +}) +it('should be possible to use apply in plugins', async () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + plugins: [ + function ({ addComponents }) { + addComponents({ + '.a': { + color: 'red', + }, + '.b': { + '@apply a': {}, + color: 'blue', + }, + }) + }, + ], + } + + return run('@tailwind components', config).then((result) => { expect(result.css).toMatchFormattedCss(css` - .foo, - .bar { - color: green; + .a { + color: red; } - `) - - result = await run(inputAfter, config) - - expect(result.css).toMatchFormattedCss(css` - .foo, - .bar { + .b { color: red; + color: #00f; } `) }) +}) + +it('should apply using the updated user CSS when the source has changed', async () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + } + + let inputBefore = css` + .foo { + color: green; + } + + .bar { + @apply foo; + } + ` - it('apply + layer utilities + selector variants (like group) + important selector', async () => { - let config = { - important: '#myselector', - content: [{ raw: html`
` }], - plugins: [], + let inputAfter = css` + .foo { + color: red; } - let input = css` - @tailwind utilities; - @layer utilities { - .custom-utility { - @apply font-normal group-hover:underline; - } - } - ` + .bar { + @apply foo; + } + ` - let result = await run(input, config) + let result = await run(inputBefore, config) - expect(result.css).toMatchFormattedCss(css` - #myselector :is(.custom-utility) { - font-weight: 400; - } - #myselector :is(.group:hover .custom-utility) { - text-decoration-line: underline; - } - `) - }) + expect(result.css).toMatchFormattedCss(css` + .foo, + .bar { + color: green; + } + `) - it('apply + user CSS + selector variants (like group) + important selector (1)', async () => { - let config = { - important: '#myselector', - content: [{ raw: html`
` }], - plugins: [], + result = await run(inputAfter, config) + + expect(result.css).toMatchFormattedCss(css` + .foo, + .bar { + color: red; } + `) +}) - let input = css` +it('apply + layer utilities + selector variants (like group) + important selector', async () => { + let config = { + important: '#myselector', + content: [{ raw: html`
` }], + plugins: [], + } + + let input = css` + @tailwind utilities; + @layer utilities { .custom-utility { @apply font-normal group-hover:underline; } - ` + } + ` - let result = await run(input, config) + let result = await run(input, config) - expect(result.css).toMatchFormattedCss(css` - .custom-utility { - font-weight: 400; - } - .group:hover .custom-utility { - text-decoration-line: underline; - } - `) - }) + expect(result.css).toMatchFormattedCss(css` + #myselector :is(.custom-utility) { + font-weight: 400; + } + #myselector :is(.group:hover .custom-utility) { + text-decoration-line: underline; + } + `) +}) - it('apply + user CSS + selector variants (like group) + important selector (2)', async () => { - let config = { - important: '#myselector', - content: [{ raw: html`
` }], - plugins: [], +it('apply + user CSS + selector variants (like group) + important selector (1)', async () => { + let config = { + important: '#myselector', + content: [{ raw: html`
` }], + plugins: [], + } + + let input = css` + .custom-utility { + @apply font-normal group-hover:underline; } + ` - let input = css` - #myselector .custom-utility { - @apply font-normal group-hover:underline; - } - ` + let result = await run(input, config) - let result = await run(input, config) + expect(result.css).toMatchFormattedCss(css` + .custom-utility { + font-weight: 400; + } + .group:hover .custom-utility { + text-decoration-line: underline; + } + `) +}) - expect(result.css).toMatchFormattedCss(css` - #myselector .custom-utility { - font-weight: 400; - } - .group:hover #myselector .custom-utility { - text-decoration-line: underline; - } - `) - }) +it('apply + user CSS + selector variants (like group) + important selector (2)', async () => { + let config = { + important: '#myselector', + content: [{ raw: html`
` }], + plugins: [], + } - it('can apply user utilities that start with a dash', async () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], + let input = css` + #myselector .custom-utility { + @apply font-normal group-hover:underline; } + ` - let input = css` - @tailwind utilities; - @layer utilities { - .foo-1 { - margin: 10px; - } - .-foo-1 { - margin: -15px; - } - .new-class { - @apply -foo-1; - } - } - ` + let result = await run(input, config) - let result = await run(input, config) + expect(result.css).toMatchFormattedCss(css` + #myselector .custom-utility { + font-weight: 400; + } + .group:hover #myselector .custom-utility { + text-decoration-line: underline; + } + `) +}) - expect(result.css).toMatchFormattedCss(css` +it('can apply user utilities that start with a dash', async () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + } + + let input = css` + @tailwind utilities; + @layer utilities { .foo-1 { margin: 10px; } - .-foo-1, - .new-class { + .-foo-1 { margin: -15px; } - `) - }) + .new-class { + @apply -foo-1; + } + } + ` - it('can apply joined classes when using elements', async () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], + let result = await run(input, config) + + expect(result.css).toMatchFormattedCss(css` + .foo-1 { + margin: 10px; + } + .-foo-1, + .new-class { + margin: -15px; } + `) +}) - let input = css` - .foo.bar { - color: red; - } - .bar.foo { - color: green; - } - header:nth-of-type(odd) { - @apply foo; - } - header::after { - @apply foo; - } - main { - @apply foo bar; - } - footer { - @apply bar; - } - ` +it('can apply joined classes when using elements', async () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + } - let result = await run(input, config) + let input = css` + .foo.bar { + color: red; + } + .bar.foo { + color: green; + } + header:nth-of-type(odd) { + @apply foo; + } + header::after { + @apply foo; + } + main { + @apply foo bar; + } + footer { + @apply bar; + } + ` - expect(result.css).toMatchFormattedCss(css` - .foo.bar { - color: red; - } - .bar.foo { - color: green; - } - header:nth-of-type(2n + 1).bar { - color: red; - } - header.bar:nth-of-type(2n + 1) { - color: green; - } - header.bar:after, - main.bar, - main.foo, - footer.foo { - color: red; - color: green; - } - `) - }) + let result = await run(input, config) - it('should not replace multiple instances of the same class in a single selector', async () => { - // NOTE: This test is non-normative and is not part of the spec of how `@apply` works per-se - // It describes how it currently works because the "correct" way produces a combinatorial explosion - // of selectors that is not easily doable - let config = { - content: [{ raw: html`
` }], - plugins: [], + expect(result.css).toMatchFormattedCss(css` + .foo.bar { + color: red; + } + .bar.foo { + color: green; } + header:nth-of-type(2n + 1).bar { + color: red; + } + header.bar:nth-of-type(2n + 1) { + color: green; + } + header.bar:after, + main.bar, + main.foo, + footer.foo { + color: red; + color: green; + } + `) +}) - let input = css` - .foo + .foo { - color: blue; - } - .bar + .bar { - color: fuchsia; - } - header { - @apply foo; - } - main { - @apply foo bar; - } - footer { - @apply bar; - } - ` +it('should not replace multiple instances of the same class in a single selector', async () => { + // NOTE: This test is non-normative and is not part of the spec of how `@apply` works per-se + // It describes how it currently works because the "correct" way produces a combinatorial explosion + // of selectors that is not easily doable + let config = { + content: [{ raw: html`
` }], + plugins: [], + } + + let input = css` + .foo + .foo { + color: blue; + } + .bar + .bar { + color: fuchsia; + } + header { + @apply foo; + } + main { + @apply foo bar; + } + footer { + @apply bar; + } + ` + + let result = await run(input, config) + + expect(result.css).toMatchFormattedCss(css` + .foo + .foo { + color: #00f; + } + .bar + .bar { + color: #f0f; + } + header + .foo, + main + .foo { + color: #00f; + } + main + .bar, + footer + .bar { + color: #f0f; + } + `) +}) + +it('should maintain the correct selector when applying other utilities', () => { + let config = { + content: [ + { + raw: html` +
+
+
+ `, + }, + ], + } + + let input = css` + @tailwind utilities; + + .foo:hover.bar .baz { + @apply bg-black; + color: red; + } - let result = await run(input, config) + .foo:hover.bar > .baz { + @apply bg-black; + color: red; + } + ` + return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` - .foo + .foo { - color: #00f; - } - .bar + .bar { - color: #f0f; - } - header + .foo, - main + .foo { - color: #00f; - } - main + .bar, - footer + .bar { - color: #f0f; + .foo:hover.bar .baz, + .foo:hover.bar > .baz { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); + color: red; } `) }) +}) - it('should maintain the correct selector when applying other utilities', () => { - let config = { - content: [ - { - raw: html` -
-
-
- `, - }, - ], +it('pseudo elements inside apply are moved outside of :is() or :has()', () => { + let config = { + darkMode: 'selector', + content: [ + { + raw: html`
`, + }, + ], + } + + let input = css` + .foo::before { + @apply dark:bg-black/100; } - let input = css` - @tailwind utilities; + .bar::before { + @apply rtl:dark:bg-black/100; + } - .foo:hover.bar .baz { - @apply bg-black; - color: red; - } + .baz::before { + @apply rtl:dark:hover:bg-black/100; + } - .foo:hover.bar > .baz { - @apply bg-black; - color: red; - } - ` + .qux::file-selector-button { + @apply rtl:dark:hover:bg-black/100; + } - return run(input, config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .foo:hover.bar .baz, - .foo:hover.bar > .baz { - --tw-bg-opacity: 1; - background-color: rgb(0 0 0 / var(--tw-bg-opacity)); - color: red; - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .foo:hover.bar .baz, - .foo:hover.bar > .baz { - color: red; - background-color: #000; - } - `) - }) - }) + .steve::before { + @apply rtl:hover:dark:bg-black/100; + } - it('pseudo elements inside apply are moved outside of :is() or :has()', () => { - let config = { - darkMode: 'selector', - content: [ - { - raw: html`
`, - }, - ], + .bob::file-selector-button { + @apply rtl:hover:dark:bg-black/100; } - let input = css` - .foo::before { - @apply dark:bg-black/100; - } + .foo::before { + @apply [:has([dir="rtl"]_&)]:hover:bg-black/100; + } - .bar::before { - @apply rtl:dark:bg-black/100; - } + .bar::file-selector-button { + @apply [:has([dir="rtl"]_&)]:hover:bg-black/100; + } + ` - .baz::before { - @apply rtl:dark:hover:bg-black/100; + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo:where(.dark, .dark *)::before, + .bar:where(.dark, .dark *):where([dir='rtl'], [dir='rtl'] *)::before, + .baz:hover:where(.dark, .dark *):where([dir='rtl'], [dir='rtl'] *)::before { + background-color: #000; } - - .qux::file-selector-button { - @apply rtl:dark:hover:bg-black/100; + .qux:where(.dark, .dark *):where([dir='rtl'], [dir='rtl'] *)::file-selector-button:hover { + background-color: #000; } - - .steve::before { - @apply rtl:hover:dark:bg-black/100; + .steve:where(.dark, .dark *):hover:where([dir='rtl'], [dir='rtl'] *):before { + background-color: #000; } - - .bob::file-selector-button { - @apply rtl:hover:dark:bg-black/100; + .bob:where(.dark, .dark *):hover:where([dir='rtl'], [dir='rtl'] *)::file-selector-button { + background-color: #000; } - - .foo::before { - @apply [:has([dir="rtl"]_&)]:hover:bg-black/100; + :has([dir='rtl'] .foo:hover):before { + background-color: #000; } - - .bar::file-selector-button { - @apply [:has([dir="rtl"]_&)]:hover:bg-black/100; + :has([dir='rtl'] .bar)::file-selector-button:hover { + background-color: #000; } - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .foo:where(.dark, .dark *)::before, - .bar:where(.dark, .dark *):where([dir='rtl'], [dir='rtl'] *)::before, - .baz:hover:where(.dark, .dark *):where([dir='rtl'], [dir='rtl'] *)::before { - background-color: #000; - } - .qux:where(.dark, .dark *):where([dir='rtl'], [dir='rtl'] *)::file-selector-button:hover { - background-color: #000; - } - .steve:where(.dark, .dark *):hover:where([dir='rtl'], [dir='rtl'] *):before { - background-color: #000; - } - .bob:where(.dark, .dark *):hover:where([dir='rtl'], [dir='rtl'] *)::file-selector-button { - background-color: #000; - } - :has([dir='rtl'] .foo:hover):before { - background-color: #000; - } - :has([dir='rtl'] .bar)::file-selector-button:hover { - background-color: #000; - } - `) - }) + `) }) +}) - stable.test('::ng-deep, ::deep, ::v-deep pseudo elements are left alone', () => { - let config = { - darkMode: 'selector', - content: [ - { - raw: html`
`, - }, - ], +test('::ng-deep, ::deep, ::v-deep pseudo elements are left alone', () => { + let config = { + darkMode: 'selector', + content: [ + { + raw: html`
`, + }, + ], + } + + let input = css` + ::ng-deep .foo .bar { + @apply font-bold; + } + ::v-deep .foo .bar { + @apply font-bold; } + ::deep .foo .bar { + @apply font-bold; + } + ` - let input = css` + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` ::ng-deep .foo .bar { - @apply font-bold; + font-weight: 700; } ::v-deep .foo .bar { - @apply font-bold; + font-weight: 700; } ::deep .foo .bar { - @apply font-bold; + font-weight: 700; } - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ::ng-deep .foo .bar { - font-weight: 700; - } - ::v-deep .foo .bar { - font-weight: 700; - } - ::deep .foo .bar { - font-weight: 700; - } - `) - }) + `) }) +}) - // 1. `::ng-deep` is deprecated - // 2. `::deep` and `::v-deep` are non-standard - // 3. They all use invalid selector syntax that Lightning CSS does not support - // It may be enough for Oxide to not support it at all - oxide.test.todo('::ng-deep, ::deep, ::v-deep pseudo elements are left alone') - - test('should not break replacing important selector when the same as the parent selector (pseudo)', async () => { - let config = { - important: ':root', - content: [], - } - - let input = css` - @tailwind components; - @layer components { - :root { - @apply flex; - } - } - ` - - let result = await run(input, config) +test('should not break replacing important selector when the same as the parent selector (pseudo)', async () => { + let config = { + important: ':root', + content: [], + } - expect(result.css).toMatchFormattedCss(css` + let input = css` + @tailwind components; + @layer components { :root { - display: flex; + @apply flex; } - `) - }) - - test('should not break replacing important selector when the same as the parent selector (class)', async () => { - let config = { - important: '.foo', - content: [ - { - raw: html`
`, - }, - ], } + ` - let input = css` - @tailwind components; - @layer components { - .foo { - @apply flex; - } - } - ` + let result = await run(input, config) - let result = await run(input, config) + expect(result.css).toMatchFormattedCss(css` + :root { + display: flex; + } + `) +}) - expect(result.css).toMatchFormattedCss(css` +test('should not break replacing important selector when the same as the parent selector (class)', async () => { + let config = { + important: '.foo', + content: [ + { + raw: html`
`, + }, + ], + } + + let input = css` + @tailwind components; + @layer components { .foo { - display: flex; + @apply flex; } - `) - }) + } + ` + + let result = await run(input, config) + + expect(result.css).toMatchFormattedCss(css` + .foo { + display: flex; + } + `) }) diff --git a/tests/arbitrary-properties.test.js b/tests/arbitrary-properties.test.js index ce52c1bc7dfe..17f0bcf1fb63 100644 --- a/tests/arbitrary-properties.test.js +++ b/tests/arbitrary-properties.test.js @@ -1,460 +1,456 @@ -import { crosscheck, run, html, css, defaults } from './util/run' - -crosscheck(() => { - test('basic arbitrary properties', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - } - - 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; - } - `) - }) +import { run, html, css, defaults } from './util/run' + +test('basic arbitrary properties', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + } + + 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; + } + `) }) +}) - test('different arbitrary properties are picked up separately', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - 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`
`, + }, + ], + 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`
`, - }, - ], - 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`
`, + }, + ], + 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`
`, - }, - ], - 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`
`, + }, + ], + 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`
`, - }, - ], - 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`
`, + }, + ], + 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`
`, - }, - ], - 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`
`, + }, + ], + 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`
`, - }, - ], - 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`
`, + }, + ], + 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`
`, - }, - ], - 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`
`, + }, + ], + 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`
`, - }, - ], - 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`
`, + }, + ], + 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`
`, - }, - ], - 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`
`, + }, + ], + 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`
`, - }, - ], - 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`
`, + }, + ], + 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`
`, - }, - ], - 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`
`, + }, + ], + 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`
`, - }, - ], - 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`
`, + }, + ], + 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`
` }], - 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`
` }], + 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`
` }], - 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`
` }], + 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` -
-
-
-
-
- `, - - // 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` +
+
+
+
+
+ `, + + // 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; + } + `) }) }) diff --git a/tests/arbitrary-values.test.css b/tests/arbitrary-values.test.css index 6f43a5277cc6..5a03f38dbf1b 100644 --- a/tests/arbitrary-values.test.css +++ b/tests/arbitrary-values.test.css @@ -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-\[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\,123\,123\)\] { +.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; } } + diff --git a/tests/arbitrary-values.test.js b/tests/arbitrary-values.test.js index 68d74a2593cd..bdb81397ce2f 100644 --- a/tests/arbitrary-values.test.js +++ b/tests/arbitrary-values.test.js @@ -1,656 +1,599 @@ import fs from 'fs' import path from 'path' -import { crosscheck, run, html, css } from './util/run' - -crosscheck(({ stable, oxide }) => { - test('arbitrary values', () => { - let config = { - content: [path.resolve(__dirname, './arbitrary-values.test.html')], - } - - return run('@tailwind utilities', config).then((result) => { - stable - .expect(result.css) - .toMatchFormattedCss( - fs.readFileSync(path.resolve(__dirname, './arbitrary-values.test.css'), 'utf8') - ) - }) +import { run, html, css } from './util/run' + +test('arbitrary values', () => { + let config = { + content: [path.resolve(__dirname, './arbitrary-values.test.html')], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss( + fs.readFileSync(path.resolve(__dirname, './arbitrary-values.test.css'), 'utf8') + ) }) +}) + +it('should be possible to differentiate between decoration utilities', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + } - oxide.test.todo( - 'should only detect classes with arbitrary values that are properly terminated after the arbitrary value' - ) - stable.test.todo( - 'should only detect classes with arbitrary values that are properly terminated after the arbitrary value' - ) - /* - stable.test( - 'should only detect classes with arbitrary values that are properly terminated after the arbitrary value', - () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css``) - }) - } - ) - */ - - it('should be possible to differentiate between decoration utilities', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .decoration-\[\#ccc\] { - text-decoration-color: #ccc; - } - .decoration-\[3px\] { - text-decoration-thickness: 3px; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .decoration-\[\#ccc\] { + text-decoration-color: #ccc; + } + .decoration-\[3px\] { + text-decoration-thickness: 3px; + } + `) }) +}) - it('should support modifiers for arbitrary values that contain the separator', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .hover\:bg-\[url\(\'https\:\/\/github\.com\/tailwindlabs\.png\'\)\]:hover { - background-image: url('https://github.com/tailwindlabs.png'); - } - `) - }) +it('should support modifiers for arbitrary values that contain the separator', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .hover\:bg-\[url\(\'https\:\/\/github\.com\/tailwindlabs\.png\'\)\]:hover { + background-image: url('https://github.com/tailwindlabs.png'); + } + `) }) +}) + +it('should support arbitrary values for various background utilities', () => { + let config = { + content: [ + { + raw: html` + +
+
+ + +
+
+
+
+ + +
+
+ `, + }, + ], + } - it('should support arbitrary values for various background utilities', () => { - let config = { - content: [ - { - raw: html` - -
-
- - -
-
-
-
- - -
-
- `, - }, - ], - } - - return run('@tailwind utilities', config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .bg-\[\#ff0000\] { - --tw-bg-opacity: 1; - background-color: rgb(255 0 0 / var(--tw-bg-opacity)); - } - .bg-\[color\:var\(--bg-color\)\] { - background-color: var(--bg-color); - } - .bg-\[hsl\(var\(--bg-color\)\)\] { - background-color: hsl(var(--bg-color)); - } - .bg-\[rgb\(var\(--bg-color\)\)\] { - background-color: rgb(var(--bg-color)); - } - .bg-red-500 { - --tw-bg-opacity: 1; - background-color: rgb(239 68 68 / var(--tw-bg-opacity)); - } - .bg-\[url\(\'\/image-1-0\.png\'\)\] { - background-image: url('/image-1-0.png'); - } - .bg-\[url\:var\(--image-url\)\] { - background-image: var(--image-url); - } - .bg-gradient-to-r { - background-image: linear-gradient(to right, var(--tw-gradient-stops)); - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .bg-\[\#ff0000\] { - background-color: red; - } - .bg-\[color\:var\(--bg-color\)\] { - background-color: var(--bg-color); - } - .bg-\[hsl\(var\(--bg-color\)\)\] { - background-color: hsl(var(--bg-color)); - } - .bg-\[rgb\(var\(--bg-color\)\)\] { - background-color: rgb(var(--bg-color)); - } - .bg-red-500 { - background-color: #ef4444; - } - .bg-\[url\(\'\/image-1-0\.png\'\)\] { - background-image: url('/image-1-0.png'); - } - .bg-\[url\:var\(--image-url\)\] { - background-image: var(--image-url); - } - .bg-gradient-to-r { - background-image: linear-gradient(to right, var(--tw-gradient-stops)); - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .bg-\[\#ff0000\] { + --tw-bg-opacity: 1; + background-color: rgb(255 0 0 / var(--tw-bg-opacity)); + } + .bg-\[color\:var\(--bg-color\)\] { + background-color: var(--bg-color); + } + .bg-\[hsl\(var\(--bg-color\)\)\] { + background-color: hsl(var(--bg-color)); + } + .bg-\[rgb\(var\(--bg-color\)\)\] { + background-color: rgb(var(--bg-color)); + } + .bg-red-500 { + --tw-bg-opacity: 1; + background-color: rgb(239 68 68 / var(--tw-bg-opacity)); + } + .bg-\[url\(\'\/image-1-0\.png\'\)\] { + background-image: url('/image-1-0.png'); + } + .bg-\[url\:var\(--image-url\)\] { + background-image: var(--image-url); + } + .bg-gradient-to-r { + background-image: linear-gradient(to right, var(--tw-gradient-stops)); + } + `) }) +}) + +it('should not generate any css if an unknown typehint is used', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + } - it('should not generate any css if an unknown typehint is used', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css``) - }) + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css``) }) +}) - it('should handle unknown typehints', () => { - let config = { content: [{ raw: html`
` }] } +it('should handle unknown typehints', () => { + let config = { content: [{ raw: html`
` }] } - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .w-\[length\:12px\] { - width: 12px; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .w-\[length\:12px\] { + width: 12px; + } + `) }) +}) - it('should convert _ to spaces', () => { - // Using custom css function here, because otherwise with String.raw, we run - // into an issue with `\2c ` escapes. If we use `\2c ` then JS complains - // about strict mode. But `\\2c ` is not what it expected. - function css(templates) { - return templates.join('') - } - - let config = { - content: [ - { - raw: html` -
-
-
-
-
-
-
-
-
-
-
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .col-\\[span_3_\\/_span_8\\] { - grid-column: span 3 / span 8; - } - .row-\\[span_3_\\/_span_8\\] { - grid-row: span 3 / span 8; - } - .m-\\[8px_4px\\] { - margin: 8px 4px; - } - .flex-\\[1_1_100\\%\\] { - flex: 100%; - } - .auto-cols-\\[minmax\\(0\\,_1fr\\)\\] { - grid-auto-columns: minmax(0, 1fr); - } - .grid-cols-\\[200px_repeat\\(auto-fill\\,minmax\\(15\\%\\,100px\\)\\)_300px\\] { - grid-template-columns: 200px repeat(auto-fill, minmax(15%, 100px)) 300px; - } - .grid-rows-\\[200px_repeat\\(auto-fill\\,minmax\\(15\\%\\,100px\\)\\)_300px\\] { - grid-template-rows: 200px repeat(auto-fill, minmax(15%, 100px)) 300px; - } - .rounded-\\[0px_4px_4px_0px\\] { - border-radius: 0 4px 4px 0; - } - .p-\\[8px_4px\\] { - padding: 8px 4px; - } - .shadow-\\[0px_0px_4px_black\\] { - --tw-shadow: 0px 0px 4px black; - --tw-shadow-colored: 0px 0px 4px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), - var(--tw-shadow); - } - .drop-shadow-\\[0px_1px_3px_black\\] { - --tw-drop-shadow: drop-shadow(0px 1px 3px black); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) - var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) - var(--tw-drop-shadow); - } - .content-\\[\\'__hello__world__\\'\\] { - --tw-content: ' hello world '; - content: var(--tw-content); - } - .content-\\[___abc____\\] { - --tw-content: abc; - content: var(--tw-content); - } - .content-\\[_hello_world_\\] { - --tw-content: hello world; - content: var(--tw-content); - } - `) - }) +it('should convert _ to spaces', () => { + // Using custom css function here, because otherwise with String.raw, we run + // into an issue with `\2c ` escapes. If we use `\2c ` then JS complains + // about strict mode. But `\\2c ` is not what it expected. + function css(templates) { + return templates.join('') + } + + let config = { + content: [ + { + raw: html` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .col-\\[span_3_\\/_span_8\\] { + grid-column: span 3 / span 8; + } + .row-\\[span_3_\\/_span_8\\] { + grid-row: span 3 / span 8; + } + .m-\\[8px_4px\\] { + margin: 8px 4px; + } + .flex-\\[1_1_100\\%\\] { + flex: 100%; + } + .auto-cols-\\[minmax\\(0\\,_1fr\\)\\] { + grid-auto-columns: minmax(0, 1fr); + } + .grid-cols-\\[200px_repeat\\(auto-fill\\,minmax\\(15\\%\\,100px\\)\\)_300px\\] { + grid-template-columns: 200px repeat(auto-fill, minmax(15%, 100px)) 300px; + } + .grid-rows-\\[200px_repeat\\(auto-fill\\,minmax\\(15\\%\\,100px\\)\\)_300px\\] { + grid-template-rows: 200px repeat(auto-fill, minmax(15%, 100px)) 300px; + } + .rounded-\\[0px_4px_4px_0px\\] { + border-radius: 0 4px 4px 0; + } + .p-\\[8px_4px\\] { + padding: 8px 4px; + } + .shadow-\\[0px_0px_4px_black\\] { + --tw-shadow: 0px 0px 4px black; + --tw-shadow-colored: 0px 0px 4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); + } + .drop-shadow-\\[0px_1px_3px_black\\] { + --tw-drop-shadow: drop-shadow(0px 1px 3px black); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) + var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) + var(--tw-drop-shadow); + } + .content-\\[\\'__hello__world__\\'\\] { + --tw-content: ' hello world '; + content: var(--tw-content); + } + .content-\\[___abc____\\] { + --tw-content: abc; + content: var(--tw-content); + } + .content-\\[_hello_world_\\] { + --tw-content: hello world; + content: var(--tw-content); + } + `) }) +}) + +it('should not convert escaped underscores with spaces', () => { + let config = { + content: [{ raw: `
` }], + corePlugins: { preflight: false }, + } - it('should not convert escaped underscores with spaces', () => { - let config = { - content: [{ raw: `
` }], - corePlugins: { preflight: false }, - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .content-\[\'snake\\_case\'\] { - --tw-content: 'snake_case'; - content: var(--tw-content); - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .content-\[\'snake\\_case\'\] { + --tw-content: 'snake_case'; + content: var(--tw-content); + } + `) }) +}) - it('should pick the fallback plugin when arbitrary values collide', () => { - let config = { - content: [ - { - raw: html` -
- -
- -
-
- `, - }, - ], - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .bg-\[var\(--unknown\)\] { - background-color: var(--unknown); - } - .bg-\[200px_100px\] { - background-position: 200px 100px; - } - `) - }) +it('should pick the fallback plugin when arbitrary values collide', () => { + let config = { + content: [ + { + raw: html` +
+ +
+ +
+
+ `, + }, + ], + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .bg-\[var\(--unknown\)\] { + background-color: var(--unknown); + } + .bg-\[200px_100px\] { + background-position: 200px 100px; + } + `) }) +}) - it('should pick the fallback plugin when arbitrary values collide and can not be inferred', () => { - let config = { - content: [{ raw: html`
` }], - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .bg-\[var\(--tw-unknown\)\] { - background-color: var(--tw-unknown); - } - `) - }) +it('should pick the fallback plugin when arbitrary values collide and can not be inferred', () => { + let config = { + content: [{ raw: html`
` }], + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .bg-\[var\(--tw-unknown\)\] { + background-color: var(--tw-unknown); + } + `) }) +}) - it('should warn and not generate if arbitrary values are ambiguous (without fallback)', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [ - function ({ matchUtilities }) { - matchUtilities({ foo: (value) => ({ value }) }, { type: ['position'] }) - matchUtilities({ foo: (value) => ({ value }) }, { type: ['size'] }) - }, - ], - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css``) - }) +it('should warn and not generate if arbitrary values are ambiguous (without fallback)', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [ + function ({ matchUtilities }) { + matchUtilities({ foo: (value) => ({ value }) }, { type: ['position'] }) + matchUtilities({ foo: (value) => ({ value }) }, { type: ['size'] }) + }, + ], + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css``) }) +}) + +it('should support colons in URLs', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + } - it('should support colons in URLs', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .bg-\[url\(\'https\:\/\/www\.spacejam\.com\/1996\/img\/bg_stars\.gif\'\)\] { - background-image: url('https://www.spacejam.com/1996/img/bg_stars.gif'); - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .bg-\[url\(\'https\:\/\/www\.spacejam\.com\/1996\/img\/bg_stars\.gif\'\)\] { + background-image: url('https://www.spacejam.com/1996/img/bg_stars.gif'); + } + `) }) +}) - it('should support unescaped underscores in URLs', () => { - let config = { - content: [ - { raw: html`
` }, - ], - } +it('should support unescaped underscores in URLs', () => { + let config = { + content: [ + { raw: html`
` }, + ], + } - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(` + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(` .bg-\\[url\\(\\'brown_potato\\.jpg\\'\\)\\,_url\\(\\'red_tomato\\.png\\'\\)\\] { background-image: url('brown_potato.jpg'), url('red_tomato.png'); } `) - }) }) +}) - it('should be possible to read theme values in arbitrary values (without quotes)', () => { - let config = { - content: [{ raw: html`
` }], - theme: { - spacing: { - 0.5: 'calc(.5 * .25rem)', - 1: 'calc(1 * .25rem)', - }, +it('should be possible to read theme values in arbitrary values (without quotes)', () => { + let config = { + content: [{ raw: html`
` }], + theme: { + spacing: { + 0.5: 'calc(.5 * .25rem)', + 1: 'calc(1 * .25rem)', }, - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .w-\[theme\(spacing\.1\)\] { - width: 0.25rem; - } - .w-\[theme\(spacing\[0\.5\]\)\] { - width: 0.125rem; - } - `) - }) + }, + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .w-\[theme\(spacing\.1\)\] { + width: 0.25rem; + } + .w-\[theme\(spacing\[0\.5\]\)\] { + width: 0.125rem; + } + `) }) +}) - it('should be possible to read theme values in arbitrary values (with quotes)', () => { - let config = { - content: [ - { raw: html`
` }, - ], - theme: { - spacing: { - 0.5: 'calc(.5 * .25rem)', - 1: 'calc(1 * .25rem)', - }, +it('should be possible to read theme values in arbitrary values (with quotes)', () => { + let config = { + content: [{ raw: html`
` }], + theme: { + spacing: { + 0.5: 'calc(.5 * .25rem)', + 1: 'calc(1 * .25rem)', }, - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .w-\[theme\(\'spacing\.1\'\)\] { - width: 0.25rem; - } - .w-\[theme\(\'spacing\[0\.5\]\'\)\] { - width: 0.125rem; - } - `) - }) + }, + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .w-\[theme\(\'spacing\.1\'\)\] { + width: 0.25rem; + } + .w-\[theme\(\'spacing\[0\.5\]\'\)\] { + width: 0.125rem; + } + `) }) +}) - it('should be possible to read theme values in arbitrary values (with quotes) when inside calc or similar functions', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - theme: { - spacing: { - 0.5: 'calc(.5 * .25rem)', - 1: 'calc(1 * .25rem)', - }, +it('should be possible to read theme values in arbitrary values (with quotes) when inside calc or similar functions', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + theme: { + spacing: { + 0.5: 'calc(.5 * .25rem)', + 1: 'calc(1 * .25rem)', }, - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .w-\[calc\(100\%-theme\(\'spacing\.1\'\)\)\] { - width: calc(100% - 0.25rem); - } - .w-\[calc\(100\%-theme\(\'spacing\[0\.5\]\'\)\)\] { - width: calc(100% - 0.125rem); - } - `) - }) + }, + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .w-\[calc\(100\%-theme\(\'spacing\.1\'\)\)\] { + width: calc(100% - 0.25rem); + } + .w-\[calc\(100\%-theme\(\'spacing\[0\.5\]\'\)\)\] { + width: calc(100% - 0.125rem); + } + `) }) +}) + +it('should not output unparsable arbitrary CSS values', () => { + let config = { + content: [ + { + raw: 'let classes = `w-[${sizes.width}]`', + }, + ], + } - it('should not output unparsable arbitrary CSS values', () => { - let config = { - content: [ - { - raw: 'let classes = `w-[${sizes.width}]`', - }, - ], - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(``) - }) + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(``) }) +}) - // Issue: https://github.com/tailwindlabs/tailwindcss/issues/7997 - // `top_right_50%` was a valid percentage before introducing this change - it('should correctly validate each part when checking for `percentage` data types', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - plugins: [], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .bg-\[top_right_50\%\] { - background-position: right 50% top; - } - `) - }) +// Issue: https://github.com/tailwindlabs/tailwindcss/issues/7997 +// `top_right_50%` was a valid percentage before introducing this change +it('should correctly validate each part when checking for `percentage` data types', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + plugins: [], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .bg-\[top_right_50\%\] { + background-position: right 50% top; + } + `) }) +}) + +it('should correctly validate background size', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + plugins: [], + } + + let input = css` + @tailwind utilities; + ` - it('should correctly validate background size', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - plugins: [], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .bg-\[auto_auto\,cover\,_contain\,10px\,10px_10\%\] { - background-size: auto, cover, contain, 10px, 10px 10%; - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .bg-\[auto_auto\,cover\,_contain\,10px\,10px_10\%\] { + background-size: auto, cover, contain, 10px, 10px 10%; + } + `) }) +}) + +it('should correctly validate combination of percentage and length', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + plugins: [], + } - it('should correctly validate combination of percentage and length', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - plugins: [], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .bg-\[50\%_10\%\] { - background-position: 50% 10%; - } - .bg-\[50px_10\%\] { - background-position: 50px 10%; - } - .bg-\[50px_10px\] { - background-position: 50px 10px; - } - `) - }) + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .bg-\[50\%_10\%\] { + background-position: 50% 10%; + } + .bg-\[50px_10\%\] { + background-position: 50px 10%; + } + .bg-\[50px_10px\] { + background-position: 50px 10px; + } + `) }) +}) - it('can explicitly specify type for percentage and length', () => { - let config = { - content: [ - { raw: html`
` }, - ], - corePlugins: { preflight: false }, - plugins: [], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .bg-\[size\:50px_10\%\] { - background-size: 50px 10%; - } - .bg-\[50px_10px\] { - background-position: 50px 10px; - } - .bg-\[position\:50\%_10\%\] { - background-position: 50% 10%; - } - `) - }) +it('can explicitly specify type for percentage and length', () => { + let config = { + content: [ + { raw: html`
` }, + ], + corePlugins: { preflight: false }, + plugins: [], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .bg-\[size\:50px_10\%\] { + background-size: 50px 10%; + } + .bg-\[50px_10px\] { + background-position: 50px 10px; + } + .bg-\[position\:50\%_10\%\] { + background-position: 50% 10%; + } + `) }) +}) + +it('can use CSS variables as arbitrary values without `var()`', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + plugins: [], + } - it('can use CSS variables as arbitrary values without `var()`', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - plugins: [], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .w-\[--width-var\] { - width: var(--width-var); - } - .bg-\[--color-var\,\#000\] { - background-color: var(--color-var, #000); - } - .bg-\[--color-var\] { - background-color: var(--color-var); - } - .bg-\[length\:--size-var\] { - background-size: var(--size-var); - } - .text-\[length\:--size-var\,12px\] { - font-size: var(--size-var, 12px); - } - `) - }) + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .w-\[--width-var\] { + width: var(--width-var); + } + .bg-\[--color-var\,\#000\] { + background-color: var(--color-var, #000); + } + .bg-\[--color-var\] { + background-color: var(--color-var); + } + .bg-\[length\:--size-var\] { + background-size: var(--size-var); + } + .text-\[length\:--size-var\,12px\] { + font-size: var(--size-var, 12px); + } + `) }) +}) + +it('can use CSS variables as arbitrary modifiers without `var()`', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + plugins: [], + } + + let input = css` + @tailwind utilities; + ` - it('can use CSS variables as arbitrary modifiers without `var()`', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - plugins: [], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .bg-red-500\/\[--opacity\] { - background-color: rgb(239 68 68 / var(--opacity)); - } - .text-sm\/\[--line-height\] { - font-size: 0.875rem; - line-height: var(--line-height); - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .bg-red-500\/\[--opacity\] { + background-color: rgb(239 68 68 / var(--opacity)); + } + .text-sm\/\[--line-height\] { + font-size: 0.875rem; + line-height: var(--line-height); + } + `) }) +}) - it('should support underscores in arbitrary modifiers', () => { - let config = { - content: [{ raw: html`
` }], - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .text-lg\/\[calc\(50px_\*_2\)\] { - font-size: 1.125rem; - line-height: calc(50px * 2); - } - `) - }) +it('should support underscores in arbitrary modifiers', () => { + let config = { + content: [{ raw: html`
` }], + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .text-lg\/\[calc\(50px_\*_2\)\] { + font-size: 1.125rem; + line-height: calc(50px * 2); + } + `) }) }) diff --git a/tests/arbitrary-variants.test.js b/tests/arbitrary-variants.test.js index c4f8960dc638..0ab9e234c336 100644 --- a/tests/arbitrary-variants.test.js +++ b/tests/arbitrary-variants.test.js @@ -1,377 +1,372 @@ -import { crosscheck, run, html, css, defaults } from './util/run' - -crosscheck(({ stable, oxide }) => { - test('basic arbitrary variants', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind base; - @tailwind components; - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults} - .\[\&\>\*\]\:underline > * { - text-decoration-line: underline; - } - `) - }) +import { run, html, css, defaults } from './util/run' + +test('basic arbitrary variants', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults} + .\[\&\>\*\]\:underline > * { + text-decoration-line: underline; + } + `) }) +}) - test('spaces in selector (using _)', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind base; - @tailwind components; - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults} - .a.b .\[\.a\.b_\&\]\:underline { - text-decoration-line: underline; - } - `) - }) +test('spaces in selector (using _)', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults} + .a.b .\[\.a\.b_\&\]\:underline { + text-decoration-line: underline; + } + `) }) +}) - test('arbitrary variants with modifiers', () => { - let config = { - content: [{ raw: html`
` }], - 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\:\[\&\>\*\]\:underline > :hover { - text-decoration-line: underline; - } +test('arbitrary variants with modifiers', () => { + let config = { + content: [{ raw: html`
` }], + 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\:\[\&\>\*\]\:underline > :hover { + text-decoration-line: underline; } } - `) - }) + } + `) }) +}) - test('variants without & or an at-rule are ignored', () => { - let config = { - content: [ - { - raw: html` -
-
-
-
- `, - }, - ], - 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('variants without & or an at-rule are ignored', () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+ `, + }, + ], + 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('arbitrary variants are sorted after other variants', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind base; - @tailwind components; - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults} - .underline { - text-decoration-line: underline; - } - @media (min-width: 1024px) { - .lg\:underline { - text-decoration-line: underline; - } - } - .\[\&\>\*\]\:underline > * { +test('arbitrary variants are sorted after other variants', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults} + .underline { + text-decoration-line: underline; + } + @media (min-width: 1024px) { + .lg\:underline { text-decoration-line: underline; } - `) - }) + } + .\[\&\>\*\]\:underline > * { + text-decoration-line: underline; + } + `) }) +}) - test('using the important modifier', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind base; - @tailwind components; - @tailwind utilities; - ` +test('using the important modifier', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults} + .\[\&\>\*\]\:\!underline > * { + text-decoration-line: underline !important; + } + `) + }) +}) - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults} - .\[\&\>\*\]\:\!underline > * { - text-decoration-line: underline !important; +test('at-rules', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults} + @supports (what: ever) { + .\[\@supports\(what\:ever\)\]\:underline { + text-decoration-line: underline; } - `) - }) + } + `) }) +}) - test('at-rules', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind base; - @tailwind components; - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults} - @supports (what: ever) { - .\[\@supports\(what\:ever\)\]\:underline { +test('nested at-rules', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + 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 screen { + @media (hover: hover) { + .\[\@media_screen\{\@media\(hover\:hover\)\}\]\:underline { text-decoration-line: underline; } } - `) - }) + } + `) }) +}) - test('nested at-rules', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - 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 screen { - @media (hover: hover) { - .\[\@media_screen\{\@media\(hover\:hover\)\}\]\:underline { - text-decoration-line: underline; - } - } +test('at-rules with selector modifications', () => { + let config = { + content: [{ raw: html`
` }], + 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 (hover: hover) { + .\[\@media\(hover\:hover\)\{\&\:hover\}\]\:underline:hover { + text-decoration-line: underline; } - `) - }) + } + `) }) +}) - test('at-rules with selector modifications', () => { - let config = { - content: [{ raw: html`
` }], - 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('nested at-rules with selector modifications', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + 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 screen { @media (hover: hover) { - .\[\@media\(hover\:hover\)\{\&\:hover\}\]\:underline:hover { + .\[\@media_screen\{\@media\(hover\:hover\)\{\&\:hover\}\}\]\:underline:hover { text-decoration-line: underline; } } - `) - }) - }) - - test('nested at-rules with selector modifications', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - 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 screen { - @media (hover: hover) { - .\[\@media_screen\{\@media\(hover\:hover\)\{\&\:hover\}\}\]\:underline:hover { - text-decoration-line: underline; - } - } - } - `) - }) + } + `) }) +}) - test('attribute selectors', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind base; - @tailwind components; - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults} - .\[\&\[data-open\]\]\:underline[data-open] { - text-decoration-line: underline; - } - `) - }) +test('attribute selectors', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults} + .\[\&\[data-open\]\]\:underline[data-open] { + text-decoration-line: underline; + } + `) }) +}) - test('multiple attribute selectors', () => { - let config = { - content: [ - { raw: html`
` }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind base; - @tailwind components; - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults} - .\[\&\[data-foo\]\[data-bar\]\:not\(\[data-baz\]\)\]\:underline[data-foo][data-bar]:not([data-baz]) { - text-decoration-line: underline; - } - `) - }) +test('multiple attribute selectors', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults} + .\[\&\[data-foo\]\[data-bar\]\:not\(\[data-baz\]\)\]\:underline[data-foo][data-bar]:not([data-baz]) { + text-decoration-line: underline; + } + `) }) +}) - oxide.test.todo('multiple attribute selectors with custom separator (1)') - stable.test('multiple attribute selectors with custom separator (1)', () => { - let config = { - separator: '__', - content: [ - { raw: html`
` }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind base; - @tailwind components; - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults} - .\[\&\[data-foo\]\[data-bar\]\:not\(\[data-baz\]\)\]__underline[data-foo][data-bar]:not([data-baz]) { - text-decoration-line: underline; - } - `) - }) +test('multiple attribute selectors with custom separator (1)', () => { + let config = { + separator: '__', + content: [ + { raw: html`
` }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults} + .\[\&\[data-foo\]\[data-bar\]\:not\(\[data-baz\]\)\]__underline[data-foo][data-bar]:not([data-baz]) { + text-decoration-line: underline; + } + `) }) +}) - oxide.test.todo('multiple attribute selectors with custom separator (2)') - stable.test('multiple attribute selectors with custom separator (2)', () => { - let config = { - separator: '_@', - content: [ - { raw: html`
` }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind base; - @tailwind components; - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults} - .\[\&\[data-foo\]\[data-bar\]\:not\(\[data-baz\]\)\]_\@underline[data-foo][data-bar]:not([data-baz]) { - text-decoration-line: underline; - } - `) - }) +test('multiple attribute selectors with custom separator (2)', () => { + let config = { + separator: '_@', + content: [ + { raw: html`
` }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults} + .\[\&\[data-foo\]\[data-bar\]\:not\(\[data-baz\]\)\]_\@underline[data-foo][data-bar]:not([data-baz]) { + text-decoration-line: underline; + } + `) }) +}) - test('with @apply', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - } +test('with @apply', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + } - let input = ` + let input = ` @tailwind base; @tailwind components; @tailwind utilities; @@ -381,138 +376,138 @@ crosscheck(({ stable, oxide }) => { } ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults} - @media screen { - @media (hover: hover) { - .foo:hover { - text-decoration-line: underline; - } + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults} + @media screen { + @media (hover: hover) { + .foo:hover { + text-decoration-line: underline; } } - `) - }) + } + `) }) +}) - test('keeps escaped underscores', () => { - let config = { - content: [ - { - raw: '
', - }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind base; - @tailwind components; - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults} - .\[\&_\.foo\\_\\_bar\]\:underline .foo__bar { - text-decoration-line: underline; - } - `) - }) +test('keeps escaped underscores', () => { + let config = { + content: [ + { + raw: '
', + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults} + .\[\&_\.foo\\_\\_bar\]\:underline .foo__bar { + text-decoration-line: underline; + } + `) }) +}) - test('keeps escaped underscores with multiple arbitrary variants', () => { - let config = { - content: [ - { - raw: '
', - }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind base; - @tailwind components; - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults} - .\[\&_\.foo\\_\\_bar\]\:\[\&_\.bar\\_\\_baz\]\:underline .bar__baz .foo__bar { - text-decoration-line: underline; - } - `) - }) +test('keeps escaped underscores with multiple arbitrary variants', () => { + let config = { + content: [ + { + raw: '
', + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults} + .\[\&_\.foo\\_\\_bar\]\:\[\&_\.bar\\_\\_baz\]\:underline .bar__baz .foo__bar { + text-decoration-line: underline; + } + `) }) +}) - test('keeps escaped underscores in arbitrary variants mixed with normal variants', () => { - let config = { - content: [ - { - raw: ` +test('keeps escaped underscores in arbitrary variants mixed with normal variants', () => { + let config = { + content: [ + { + raw: `
`, - }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind base; - @tailwind components; - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults} - .\[\&_\.foo\\_\\_bar\]\:hover\:underline:hover .foo__bar, + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults} + .\[\&_\.foo\\_\\_bar\]\:hover\:underline:hover .foo__bar, .hover\:\[\&_\.foo\\_\\_bar\]\:underline .foo__bar:hover { - text-decoration-line: underline; - } - `) - }) + text-decoration-line: underline; + } + `) }) +}) - test('allows attribute variants with quotes', () => { - let config = { - content: [ - { - raw: ` +test('allows attribute variants with quotes', () => { + let config = { + content: [ + { + raw: `
`, - }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind base; - @tailwind components; - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults} - .\[\&\[data-test\=\"2\"\]\]\:underline[data-test='2'], + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults} + .\[\&\[data-test\=\"2\"\]\]\:underline[data-test='2'], .\[\&\[data-test\=\'2\'\]\]\:underline[data-test='2'] { - text-decoration-line: underline; - } - `) - }) + text-decoration-line: underline; + } + `) }) +}) - test('classes in arbitrary variants should not be prefixed', () => { - let config = { - prefix: 'tw-', - content: [ - { - raw: ` +test('classes in arbitrary variants should not be prefixed', () => { + let config = { + prefix: 'tw-', + content: [ + { + raw: `
should not be red
should be red
@@ -530,42 +525,34 @@ crosscheck(({ stable, oxide }) => {
should be red
`, - }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .\[\&_\.foo\]\:tw-text-red-400 .foo, - .\[\&_\.foo\]\:hover\:tw-text-red-400:hover .foo, - .hover\:\[\&_\.foo\]\:tw-text-red-400 .foo:hover, - .foo .\[\.foo_\&\]\:tw-text-red-400 { - --tw-text-opacity: 1; - color: rgb(248 113 113 / var(--tw-text-opacity)); - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .\[\&_\.foo\]\:tw-text-red-400 .foo, - .\[\&_\.foo\]\:hover\:tw-text-red-400:hover .foo, - .hover\:\[\&_\.foo\]\:tw-text-red-400 .foo:hover, - .foo .\[\.foo_\&\]\:tw-text-red-400 { - color: #f87171; - } - `) - }) + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .\[\&_\.foo\]\:tw-text-red-400 .foo, + .\[\&_\.foo\]\:hover\:tw-text-red-400:hover .foo, + .hover\:\[\&_\.foo\]\:tw-text-red-400 .foo:hover, + .foo .\[\.foo_\&\]\:tw-text-red-400 { + --tw-text-opacity: 1; + color: rgb(248 113 113 / var(--tw-text-opacity)); + } + `) }) +}) - test('classes in the same arbitrary variant should not be prefixed', () => { - let config = { - prefix: 'tw-', - content: [ - { - raw: ` +test('classes in the same arbitrary variant should not be prefixed', () => { + let config = { + prefix: 'tw-', + content: [ + { + raw: `
should not be red
should be red
@@ -575,812 +562,763 @@ crosscheck(({ stable, oxide }) => {
should be red
`, - }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .\[\&_\.foo\]\:tw-bg-white .foo { - --tw-bg-opacity: 1; - background-color: rgb(255 255 255 / var(--tw-bg-opacity)); - } - .\[\&_\.foo\]\:tw-text-red-400 .foo { - --tw-text-opacity: 1; - color: rgb(248 113 113 / var(--tw-text-opacity)); - } - .foo .\[\.foo_\&\]\:tw-bg-white { - --tw-bg-opacity: 1; - background-color: rgb(255 255 255 / var(--tw-bg-opacity)); - } - .foo .\[\.foo_\&\]\:tw-text-red-400 { - --tw-text-opacity: 1; - color: rgb(248 113 113 / var(--tw-text-opacity)); - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .\[\&_\.foo\]\:tw-bg-white .foo { - background-color: #fff; - } - .\[\&_\.foo\]\:tw-text-red-400 .foo { - color: #f87171; - } - .foo .\[\.foo_\&\]\:tw-bg-white { - background-color: #fff; - } - .foo .\[\.foo_\&\]\:tw-text-red-400 { - color: #f87171; - } - `) - }) - }) - - it('should support aria variants', () => { - let config = { - content: [ - { - raw: html` -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .underline, - .aria-checked\:underline[aria-checked='true'], - .aria-\[labelledby\=\'a_b\'\]\:underline[aria-labelledby='a b'], - .aria-\[sort\=ascending\]\:underline[aria-sort='ascending'], - .group\/foo[aria-checked='true'] .group-aria-checked\/foo\:underline, - .group[aria-checked='true'] .group-aria-checked\:underline, - .group[aria-labelledby='a b'] .group-aria-\[labelledby\=\'a_b\'\]\:underline, - .group\/foo[aria-sort='ascending'] .group-aria-\[sort\=ascending\]\/foo\:underline, - .group[aria-sort='ascending'] .group-aria-\[sort\=ascending\]\:underline, - .peer\/foo[aria-checked='true'] ~ .peer-aria-checked\/foo\:underline, - .peer[aria-checked='true'] ~ .peer-aria-checked\:underline, - .peer[aria-labelledby='a b'] ~ .peer-aria-\[labelledby\=\'a_b\'\]\:underline, - .peer\/foo[aria-sort='ascending'] ~ .peer-aria-\[sort\=ascending\]\/foo\:underline, - .peer[aria-sort='ascending'] ~ .peer-aria-\[sort\=ascending\]\:underline { - text-decoration-line: underline; - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .aria-checked\:underline[aria-checked='true'], - .aria-\[labelledby\=\'a_b\'\]\:underline[aria-labelledby='a b'], - .aria-\[sort\=ascending\]\:underline[aria-sort='ascending'], - .group\/foo[aria-checked='true'] .group-aria-checked\/foo\:underline, - .group[aria-checked='true'] .group-aria-checked\:underline, - .group[aria-labelledby='a b'] .group-aria-\[labelledby\=\'a_b\'\]\:underline, - .group\/foo[aria-sort='ascending'] .group-aria-\[sort\=ascending\]\/foo\:underline, - .group[aria-sort='ascending'] .group-aria-\[sort\=ascending\]\:underline, - .peer\/foo[aria-checked='true'] ~ .peer-aria-checked\/foo\:underline, - .peer[aria-checked='true'] ~ .peer-aria-checked\:underline, - .peer[aria-labelledby='a b'] ~ .peer-aria-\[labelledby\=\'a_b\'\]\:underline, - .peer\/foo[aria-sort='ascending'] ~ .peer-aria-\[sort\=ascending\]\/foo\:underline, - .peer[aria-sort='ascending'] ~ .peer-aria-\[sort\=ascending\]\:underline { - text-decoration-line: underline; - } - `) - }) + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .\[\&_\.foo\]\:tw-bg-white .foo { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); + } + .\[\&_\.foo\]\:tw-text-red-400 .foo { + --tw-text-opacity: 1; + color: rgb(248 113 113 / var(--tw-text-opacity)); + } + .foo .\[\.foo_\&\]\:tw-bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); + } + .foo .\[\.foo_\&\]\:tw-text-red-400 { + --tw-text-opacity: 1; + color: rgb(248 113 113 / var(--tw-text-opacity)); + } + `) }) +}) - it('should support data variants', () => { - let config = { - theme: { - data: { - checked: 'ui~="checked"', - }, +it('should support aria variants', () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `, }, - content: [ - { - raw: html` -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .underline, - .data-checked\:underline[data-ui~='checked'], - .data-\[foo\=\'bar_baz\'\]\:underline[data-foo='bar baz'], - .data-\[position\=top\]\:underline[data-position='top'], - .group\/foo[data-ui~='checked'] .group-data-checked\/foo\:underline, - .group[data-ui~='checked'] .group-data-checked\:underline, - .group[data-foo='bar baz'] .group-data-\[foo\=\'bar_baz\'\]\:underline, - .group\/foo[data-position='top'] .group-data-\[position\=top\]\/foo\:underline, - .group[data-position='top'] .group-data-\[position\=top\]\:underline, - .peer\/foo[data-ui~='checked'] ~ .peer-data-checked\/foo\:underline, - .peer[data-ui~='checked'] ~ .peer-data-checked\:underline, - .peer[data-foo='bar baz'] ~ .peer-data-\[foo\=\'bar_baz\'\]\:underline, - .peer\/foo[data-position='top'] ~ .peer-data-\[position\=top\]\/foo\:underline, - .peer[data-position='top'] ~ .peer-data-\[position\=top\]\:underline { - text-decoration-line: underline; - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .data-checked\:underline[data-ui~='checked'], - .data-\[foo\=\'bar_baz\'\]\:underline[data-foo='bar baz'], - .data-\[position\=top\]\:underline[data-position='top'], - .group\/foo[data-ui~='checked'] .group-data-checked\/foo\:underline, - .group[data-ui~='checked'] .group-data-checked\:underline, - .group[data-foo='bar baz'] .group-data-\[foo\=\'bar_baz\'\]\:underline, - .group\/foo[data-position='top'] .group-data-\[position\=top\]\/foo\:underline, - .group[data-position='top'] .group-data-\[position\=top\]\:underline, - .peer\/foo[data-ui~='checked'] ~ .peer-data-checked\/foo\:underline, - .peer[data-ui~='checked'] ~ .peer-data-checked\:underline, - .peer[data-foo='bar baz'] ~ .peer-data-\[foo\=\'bar_baz\'\]\:underline, - .peer\/foo[data-position='top'] ~ .peer-data-\[position\=top\]\/foo\:underline, - .peer[data-position='top'] ~ .peer-data-\[position\=top\]\:underline { - text-decoration-line: underline; - } - `) - }) + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .underline, + .aria-checked\:underline[aria-checked='true'], + .aria-\[labelledby\=\'a_b\'\]\:underline[aria-labelledby='a b'], + .aria-\[sort\=ascending\]\:underline[aria-sort='ascending'], + .group\/foo[aria-checked='true'] .group-aria-checked\/foo\:underline, + .group[aria-checked='true'] .group-aria-checked\:underline, + .group[aria-labelledby='a b'] .group-aria-\[labelledby\=\'a_b\'\]\:underline, + .group\/foo[aria-sort='ascending'] .group-aria-\[sort\=ascending\]\/foo\:underline, + .group[aria-sort='ascending'] .group-aria-\[sort\=ascending\]\:underline, + .peer\/foo[aria-checked='true'] ~ .peer-aria-checked\/foo\:underline, + .peer[aria-checked='true'] ~ .peer-aria-checked\:underline, + .peer[aria-labelledby='a b'] ~ .peer-aria-\[labelledby\=\'a_b\'\]\:underline, + .peer\/foo[aria-sort='ascending'] ~ .peer-aria-\[sort\=ascending\]\/foo\:underline, + .peer[aria-sort='ascending'] ~ .peer-aria-\[sort\=ascending\]\:underline { + text-decoration-line: underline; + } + `) }) +}) - it('should support supports', () => { - let config = { - theme: { - supports: { - grid: 'display: grid', - }, +it('should support data variants', () => { + let config = { + theme: { + data: { + checked: 'ui~="checked"', }, - content: [ - { - raw: html` -
- -
- -
- -
- -
- -
- -
- -
- -
-
- `, - }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - @supports (display: grid) { - .supports-grid\:underline { - text-decoration-line: underline; - } - .supports-\[display\:grid\]\:grid { - display: grid; - } - } - @supports (foo: bar) and (bar: baz) { - .supports-\[\(foo\:bar\)and\(bar\:baz\)\]\:underline { - text-decoration-line: underline; - } - } - @supports (foo: bar) or (bar: baz) { - .supports-\[\(foo\:bar\)or\(bar\:baz\)\]\:underline { - text-decoration-line: underline; - } - } - @supports (container-type: var(--tw)) { - .supports-\[container-type\]\:underline { - text-decoration-line: underline; - } - } - @supports not (foo: bar) { - .supports-\[not\(foo\:bar\)\]\:underline { - text-decoration-line: underline; - } - } - @supports selector(A > B) { - .supports-\[selector\(A_\>_B\)\]\:underline { - text-decoration-line: underline; - } - } - @supports (transform-origin: 5% 5%) { - .supports-\[transform-origin\:5\%_5\%\]\:underline { - text-decoration-line: underline; - } - } - `) - }) + }, + content: [ + { + raw: html` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .underline, + .data-checked\:underline[data-ui~='checked'], + .data-\[foo\=\'bar_baz\'\]\:underline[data-foo='bar baz'], + .data-\[position\=top\]\:underline[data-position='top'], + .group\/foo[data-ui~='checked'] .group-data-checked\/foo\:underline, + .group[data-ui~='checked'] .group-data-checked\:underline, + .group[data-foo='bar baz'] .group-data-\[foo\=\'bar_baz\'\]\:underline, + .group\/foo[data-position='top'] .group-data-\[position\=top\]\/foo\:underline, + .group[data-position='top'] .group-data-\[position\=top\]\:underline, + .peer\/foo[data-ui~='checked'] ~ .peer-data-checked\/foo\:underline, + .peer[data-ui~='checked'] ~ .peer-data-checked\:underline, + .peer[data-foo='bar baz'] ~ .peer-data-\[foo\=\'bar_baz\'\]\:underline, + .peer\/foo[data-position='top'] ~ .peer-data-\[position\=top\]\/foo\:underline, + .peer[data-position='top'] ~ .peer-data-\[position\=top\]\:underline { + text-decoration-line: underline; + } + `) }) +}) - test('has-* variants with arbitrary values', () => { - let config = { - theme: {}, - content: [ - { - raw: html` -
-
-
-
-
-
-
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .has-\[\.foo\:hover\]\:block:has(.foo:hover) { - display: block; +it('should support supports', () => { + let config = { + theme: { + supports: { + grid: 'display: grid', + }, + }, + content: [ + { + raw: html` +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ `, + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + @supports (display: grid) { + .supports-grid\:underline { + text-decoration-line: underline; } - .has-\[figcaption\]\:inline-block:has(figcaption) { - display: inline-block; + .supports-\[display\:grid\]\:grid { + display: grid; } - .has-\[\[data-active\]\]\:inline:has([data-active]) { - display: inline; + } + @supports (foo: bar) and (bar: baz) { + .supports-\[\(foo\:bar\)and\(bar\:baz\)\]\:underline { + text-decoration-line: underline; } - .has-\[\.foo\]\:flex:has(.foo) { - display: flex; + } + @supports (foo: bar) or (bar: baz) { + .supports-\[\(foo\:bar\)or\(bar\:baz\)\]\:underline { + text-decoration-line: underline; } - .has-\[\>_\.potato\]\:table:has(> .potato) { - display: table; + } + @supports (container-type: var(--tw)) { + .supports-\[container-type\]\:underline { + text-decoration-line: underline; } - .has-\[\+_h2\]\:grid:has(+ h2) { - display: grid; + } + @supports not (foo: bar) { + .supports-\[not\(foo\:bar\)\]\:underline { + text-decoration-line: underline; } - .has-\[\>_h1_\+_h2\]\:contents:has(> h1 + h2) { - display: contents; + } + @supports selector(A > B) { + .supports-\[selector\(A_\>_B\)\]\:underline { + text-decoration-line: underline; } - .has-\[h2\]\:has-\[\.banana\]\:hidden:has(.banana):has(h2) { - display: none; + } + @supports (transform-origin: 5% 5%) { + .supports-\[transform-origin\:5\%_5\%\]\:underline { + text-decoration-line: underline; } - `) - }) + } + `) }) +}) - test('group-has-* variants with arbitrary values', () => { - let config = { - theme: {}, - content: [ - { - raw: html` -
-
-
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .group:has(> h1 + .foo) .group-has-\[\>_h1_\+_\.foo\]\:block { - display: block; - } - .group\/two:has(> h1 + .foo) .group-has-\[\>_h1_\+_\.foo\]\/two\:flex { - display: flex; - } - `) - }) +test('has-* variants with arbitrary values', () => { + let config = { + theme: {}, + content: [ + { + raw: html` +
+
+
+
+
+
+
+
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .has-\[\.foo\:hover\]\:block:has(.foo:hover) { + display: block; + } + .has-\[figcaption\]\:inline-block:has(figcaption) { + display: inline-block; + } + .has-\[\[data-active\]\]\:inline:has([data-active]) { + display: inline; + } + .has-\[\.foo\]\:flex:has(.foo) { + display: flex; + } + .has-\[\>_\.potato\]\:table:has(> .potato) { + display: table; + } + .has-\[\+_h2\]\:grid:has(+ h2) { + display: grid; + } + .has-\[\>_h1_\+_h2\]\:contents:has(> h1 + h2) { + display: contents; + } + .has-\[h2\]\:has-\[\.banana\]\:hidden:has(.banana):has(h2) { + display: none; + } + `) }) +}) - test('peer-has-* variants with arbitrary values', () => { - let config = { - theme: {}, - content: [ - { - raw: html` -
-
-
-
-
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .peer:has(> h1 + .foo) ~ .peer-has-\[\>_h1_\+_\.foo\]\:block { - display: block; - } - .peer\/two:has(> h1 + .foo) ~ .peer-has-\[\>_h1_\+_\.foo\]\/two\:flex { - display: flex; - } - `) - }) +test('group-has-* variants with arbitrary values', () => { + let config = { + theme: {}, + content: [ + { + raw: html` +
+
+
+
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .group:has(> h1 + .foo) .group-has-\[\>_h1_\+_\.foo\]\:block { + display: block; + } + .group\/two:has(> h1 + .foo) .group-has-\[\>_h1_\+_\.foo\]\/two\:flex { + display: flex; + } + `) }) +}) - it('should be possible to use modifiers and arbitrary groups', () => { - let config = { - content: [ - { - raw: html` -
-
- -
- - - -
- -
- - - -
- -
- - - -
- -
-
- - -
-
- -
-
- -
-
- -
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .group\/foo:hover .group-hover\/foo\:underline, - .group:hover .group-hover\:underline, - .group\/foo:focus .group-\[\&\:focus\]\/foo\:underline, - .group:focus .group-\[\&\:focus\]\:underline, - .group\/foo[data-open] .group-\[\&\[data-open\]\]\/foo\:underline, - .group[data-open] .group-\[\&\[data-open\]\]\:underline, - .group\/foo.in-foo .group-\[\.in-foo\]\/foo\:underline, - .group.in-foo .group-\[\.in-foo\]\:underline, - .in-foo .group\/foo .group-\[\.in-foo_\&\]\/foo\:underline, - .in-foo .group .group-\[\.in-foo_\&\]\:underline, - .group\/foo:hover .group-\[\:hover\]\/foo\:underline, - .group:hover .group-\[\:hover\]\:underline, - .group\/foo[data-open] .group-\[\[data-open\]\]\/foo\:underline, - .group[data-open] .group-\[\[data-open\]\]\:underline { - text-decoration-line: underline; - } - `) - }) +test('peer-has-* variants with arbitrary values', () => { + let config = { + theme: {}, + content: [ + { + raw: html` +
+
+
+
+
+
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .peer:has(> h1 + .foo) ~ .peer-has-\[\>_h1_\+_\.foo\]\:block { + display: block; + } + .peer\/two:has(> h1 + .foo) ~ .peer-has-\[\>_h1_\+_\.foo\]\/two\:flex { + display: flex; + } + `) }) +}) - it('should be possible to use modifiers and arbitrary peers', () => { - let config = { - content: [ - { - raw: html` -
-
- - -
+it('should be possible to use modifiers and arbitrary groups', () => { + let config = { + content: [ + { + raw: html` +
+
+ +
- + -
+
-
+
- + -
+
-
+
-
+
-
- - -
+
+
-
+ +
+
-
-
+
+
-
-
+
+
-
-
+
+
- `, - }, - ], - corePlugins: { preflight: false }, - } +
+ `, + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .group\/foo:hover .group-hover\/foo\:underline, + .group:hover .group-hover\:underline, + .group\/foo:focus .group-\[\&\:focus\]\/foo\:underline, + .group:focus .group-\[\&\:focus\]\:underline, + .group\/foo[data-open] .group-\[\&\[data-open\]\]\/foo\:underline, + .group[data-open] .group-\[\&\[data-open\]\]\:underline, + .group\/foo.in-foo .group-\[\.in-foo\]\/foo\:underline, + .group.in-foo .group-\[\.in-foo\]\:underline, + .in-foo .group\/foo .group-\[\.in-foo_\&\]\/foo\:underline, + .in-foo .group .group-\[\.in-foo_\&\]\:underline, + .group\/foo:hover .group-\[\:hover\]\/foo\:underline, + .group:hover .group-\[\:hover\]\:underline, + .group\/foo[data-open] .group-\[\[data-open\]\]\/foo\:underline, + .group[data-open] .group-\[\[data-open\]\]\:underline { + text-decoration-line: underline; + } + `) + }) +}) - let input = css` - @tailwind utilities; - ` +it('should be possible to use modifiers and arbitrary peers', () => { + let config = { + content: [ + { + raw: html` +
+
- return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .peer\/foo:hover ~ .peer-hover\/foo\:underline, - .peer:hover ~ .peer-hover\:underline, - .peer\/foo:focus ~ .peer-\[\&\:focus\]\/foo\:underline, - .peer:focus ~ .peer-\[\&\:focus\]\:underline, - .peer\/foo[data-open] ~ .peer-\[\&\[data-open\]\]\/foo\:underline, - .peer[data-open] ~ .peer-\[\&\[data-open\]\]\:underline, - .peer\/foo.in-foo ~ .peer-\[\.in-foo\]\/foo\:underline, - .peer.in-foo ~ .peer-\[\.in-foo\]\:underline, - .in-foo .peer\/foo ~ .peer-\[\.in-foo_\&\]\/foo\:underline, - .in-foo .peer ~ .peer-\[\.in-foo_\&\]\:underline, - .peer\/foo:hover ~ .peer-\[\:hover\]\/foo\:underline, - .peer:hover ~ .peer-\[\:hover\]\:underline, - .peer\/foo[data-open] ~ .peer-\[\[data-open\]\]\/foo\:underline, - .peer[data-open] ~ .peer-\[\[data-open\]\]\:underline { - text-decoration-line: underline; - } - `) - }) - }) + +
- it('Arbitrary variants are ordered alphabetically', () => { - let config = { - content: [ - { - raw: html` -
-
-
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - } + + +
+ +
- let input = css` - @tailwind utilities; - ` + + +
+ +
- return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .\[\&\:\:a\]\:underline::a { - text-decoration-line: underline; - } - .\[\&\:\:b\]\:underline::b { - text-decoration-line: underline; - } - .\[\&\:\:c\]\:underline::c { - text-decoration-line: underline; - } - `) - }) - }) + + +
+ +
- it('Arbitrary variants support multiple attribute selectors', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - } + +
- let input = css` - @tailwind utilities; - ` +
- return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - [data-foo='bar'][data-baz] .\[\[data-foo\=\'bar\'\]\[data-baz\]_\&\]\:underline { - text-decoration-line: underline; - } - `) - }) - }) +
+
- it('Invalid arbitrary variants selectors should produce nothing instead of failing', () => { - let config = { - content: [ - { - raw: html` -
+
+
+ +
+
+
`, - }, - ], - corePlugins: { preflight: false }, - } + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .peer\/foo:hover ~ .peer-hover\/foo\:underline, + .peer:hover ~ .peer-hover\:underline, + .peer\/foo:focus ~ .peer-\[\&\:focus\]\/foo\:underline, + .peer:focus ~ .peer-\[\&\:focus\]\:underline, + .peer\/foo[data-open] ~ .peer-\[\&\[data-open\]\]\/foo\:underline, + .peer[data-open] ~ .peer-\[\&\[data-open\]\]\:underline, + .peer\/foo.in-foo ~ .peer-\[\.in-foo\]\/foo\:underline, + .peer.in-foo ~ .peer-\[\.in-foo\]\:underline, + .in-foo .peer\/foo ~ .peer-\[\.in-foo_\&\]\/foo\:underline, + .in-foo .peer ~ .peer-\[\.in-foo_\&\]\:underline, + .peer\/foo:hover ~ .peer-\[\:hover\]\/foo\:underline, + .peer:hover ~ .peer-\[\:hover\]\:underline, + .peer\/foo[data-open] ~ .peer-\[\[data-open\]\]\/foo\:underline, + .peer[data-open] ~ .peer-\[\[data-open\]\]\:underline { + text-decoration-line: underline; + } + `) + }) +}) - let input = css` - @tailwind utilities; - ` +it('Arbitrary variants are ordered alphabetically', () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .\[\&\:\:a\]\:underline::a { + text-decoration-line: underline; + } + .\[\&\:\:b\]\:underline::b { + text-decoration-line: underline; + } + .\[\&\:\:c\]\:underline::c { + text-decoration-line: underline; + } + `) + }) +}) - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css``) - }) +it('Arbitrary variants support multiple attribute selectors', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + [data-foo='bar'][data-baz] .\[\[data-foo\=\'bar\'\]\[data-baz\]_\&\]\:underline { + text-decoration-line: underline; + } + `) }) +}) - it('should output responsive variants + stacked variants in the right order', () => { - let config = { - content: [ - { - raw: html` -
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - } +it('Invalid arbitrary variants selectors should produce nothing instead of failing', () => { + let config = { + content: [ + { + raw: html` +
+ `, + }, + ], + corePlugins: { preflight: false }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - @media (min-width: 1280px) { - .xl\:p-1 { - padding: 0.25rem; - } - } - .\[\&_ul\]\:flex ul { - display: flex; - } - .\[\&_ul\]\:flex-col ul { - flex-direction: column; - } - @media (min-width: 768px) { - .md\:\[\&_ul\]\:flex-row ul { - flex-direction: row; - } - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css``) }) +}) - it('it should discard arbitrary variants with multiple selectors', () => { - let config = { - content: [ - { - raw: html` -
-
-
-
-
-
-
-
-
-
-
-
-
-
- `, - }, - { - // escaped commas are a-ok - // This is separate because prettier complains about `\,` in the template string - raw: '
', - }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .p-1, - .span\,div .hover\:\[\.span\\\,div_\&\]\:p-1:hover, - :is(span, div) .hover\:\[\:is\(span\,div\)_\&\]\:p-1:hover, - div .\[div_\&\]\:p-1, - div .hover\:\[div_\&\]\:p-1:hover { +it('should output responsive variants + stacked variants in the right order', () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + @media (min-width: 1280px) { + .xl\:p-1 { padding: 0.25rem; } - `) - }) - }) - - it('should sort multiple variant fns with normal variants between them', () => { - /** @type {string[]} */ - let lines = [] - - for (let a of [1, 2]) { - for (let b of [2, 1]) { - for (let c of [1, 2]) { - for (let d of [2, 1]) { - for (let e of [1, 2]) { - lines.push(`
`) - } - } + } + .\[\&_ul\]\:flex ul { + display: flex; + } + .\[\&_ul\]\:flex-col ul { + flex-direction: column; + } + @media (min-width: 768px) { + .md\:\[\&_ul\]\:flex-row ul { + flex-direction: row; } } - } - - // Fisher-Yates shuffle - for (let i = lines.length - 1; i > 0; i--) { - let j = Math.floor(Math.random() * i) - ;[lines[i], lines[j]] = [lines[j], lines[i]] - } + `) + }) +}) - let config = { - content: [ - { - raw: lines.join('\n'), - }, - ], - corePlugins: { preflight: false }, - plugins: [ - function ({ addVariant, matchVariant }) { - addVariant('foo1', "&[data-foo='1']") - addVariant('foo2', "&[data-foo='2']") - - matchVariant('bar', (value) => `&[data-bar='${value}']`, { - sort: (a, b) => b.value - a.value, - }) - - addVariant('baz1', "&[data-baz='1']") - addVariant('baz2', "&[data-baz='2']") - - matchVariant('qux', (value) => `&[data-qux='${value}']`, { - sort: (a, b) => b.value - a.value, - }) - - addVariant('fred1', "&[data-fred='1']") - addVariant('fred2', "&[data-fred='2']") - }, - ], - } +it('it should discard arbitrary variants with multiple selectors', () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `, + }, + { + // escaped commas are a-ok + // This is separate because prettier complains about `\,` in the template string + raw: '
', + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .p-1, + .span\,div .hover\:\[\.span\\\,div_\&\]\:p-1:hover, + :is(span, div) .hover\:\[\:is\(span\,div\)_\&\]\:p-1:hover, + div .\[div_\&\]\:p-1, + div .hover\:\[div_\&\]\:p-1:hover { + padding: 0.25rem; + } + `) + }) +}) - let input = css` - @tailwind utilities; - ` +it('should sort multiple variant fns with normal variants between them', () => { + /** @type {string[]} */ + let lines = [] - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .fred1\:qux-\[2\]\:baz1\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='1'][data-qux='2'][data-fred='1'], - .fred1\:qux-\[2\]\:baz1\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='1'][data-qux='2'][data-fred='1'], - .fred1\:qux-\[2\]\:baz1\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='1'][data-qux='2'][data-fred='1'], - .fred1\:qux-\[2\]\:baz1\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='1'][data-qux='2'][data-fred='1'], - .fred1\:qux-\[2\]\:baz2\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='2'][data-qux='2'][data-fred='1'], - .fred1\:qux-\[2\]\:baz2\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='2'][data-qux='2'][data-fred='1'], - .fred1\:qux-\[2\]\:baz2\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='2'][data-qux='2'][data-fred='1'], - .fred1\:qux-\[2\]\:baz2\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='2'][data-qux='2'][data-fred='1'], - .fred1\:qux-\[1\]\:baz1\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='1'][data-qux='1'][data-fred='1'], - .fred1\:qux-\[1\]\:baz1\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='1'][data-qux='1'][data-fred='1'], - .fred1\:qux-\[1\]\:baz1\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='1'][data-qux='1'][data-fred='1'], - .fred1\:qux-\[1\]\:baz1\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='1'][data-qux='1'][data-fred='1'], - .fred1\:qux-\[1\]\:baz2\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='2'][data-qux='1'][data-fred='1'], - .fred1\:qux-\[1\]\:baz2\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='2'][data-qux='1'][data-fred='1'], - .fred1\:qux-\[1\]\:baz2\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='2'][data-qux='1'][data-fred='1'], - .fred1\:qux-\[1\]\:baz2\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='2'][data-qux='1'][data-fred='1'], - .fred2\:qux-\[2\]\:baz1\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='1'][data-qux='2'][data-fred='2'], - .fred2\:qux-\[2\]\:baz1\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='1'][data-qux='2'][data-fred='2'], - .fred2\:qux-\[2\]\:baz1\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='1'][data-qux='2'][data-fred='2'], - .fred2\:qux-\[2\]\:baz1\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='1'][data-qux='2'][data-fred='2'], - .fred2\:qux-\[2\]\:baz2\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='2'][data-qux='2'][data-fred='2'], - .fred2\:qux-\[2\]\:baz2\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='2'][data-qux='2'][data-fred='2'], - .fred2\:qux-\[2\]\:baz2\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='2'][data-qux='2'][data-fred='2'], - .fred2\:qux-\[2\]\:baz2\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='2'][data-qux='2'][data-fred='2'], - .fred2\:qux-\[1\]\:baz1\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='1'][data-qux='1'][data-fred='2'], - .fred2\:qux-\[1\]\:baz1\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='1'][data-qux='1'][data-fred='2'], - .fred2\:qux-\[1\]\:baz1\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='1'][data-qux='1'][data-fred='2'], - .fred2\:qux-\[1\]\:baz1\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='1'][data-qux='1'][data-fred='2'], - .fred2\:qux-\[1\]\:baz2\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='2'][data-qux='1'][data-fred='2'], - .fred2\:qux-\[1\]\:baz2\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='2'][data-qux='1'][data-fred='2'], - .fred2\:qux-\[1\]\:baz2\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='2'][data-qux='1'][data-fred='2'], - .fred2\:qux-\[1\]\:baz2\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='2'][data-qux='1'][data-fred='2'] { - padding: 0.25rem; + for (let a of [1, 2]) { + for (let b of [2, 1]) { + for (let c of [1, 2]) { + for (let d of [2, 1]) { + for (let e of [1, 2]) { + lines.push(`
`) + } } - `) - }) + } + } + } + + // Fisher-Yates shuffle + for (let i = lines.length - 1; i > 0; i--) { + let j = Math.floor(Math.random() * i) + ;[lines[i], lines[j]] = [lines[j], lines[i]] + } + + let config = { + content: [ + { + raw: lines.join('\n'), + }, + ], + corePlugins: { preflight: false }, + plugins: [ + function ({ addVariant, matchVariant }) { + addVariant('foo1', "&[data-foo='1']") + addVariant('foo2', "&[data-foo='2']") + + matchVariant('bar', (value) => `&[data-bar='${value}']`, { + sort: (a, b) => b.value - a.value, + }) + + addVariant('baz1', "&[data-baz='1']") + addVariant('baz2', "&[data-baz='2']") + + matchVariant('qux', (value) => `&[data-qux='${value}']`, { + sort: (a, b) => b.value - a.value, + }) + + addVariant('fred1', "&[data-fred='1']") + addVariant('fred2', "&[data-fred='2']") + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .fred1\:qux-\[2\]\:baz1\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='1'][data-qux='2'][data-fred='1'], + .fred1\:qux-\[2\]\:baz1\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='1'][data-qux='2'][data-fred='1'], + .fred1\:qux-\[2\]\:baz1\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='1'][data-qux='2'][data-fred='1'], + .fred1\:qux-\[2\]\:baz1\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='1'][data-qux='2'][data-fred='1'], + .fred1\:qux-\[2\]\:baz2\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='2'][data-qux='2'][data-fred='1'], + .fred1\:qux-\[2\]\:baz2\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='2'][data-qux='2'][data-fred='1'], + .fred1\:qux-\[2\]\:baz2\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='2'][data-qux='2'][data-fred='1'], + .fred1\:qux-\[2\]\:baz2\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='2'][data-qux='2'][data-fred='1'], + .fred1\:qux-\[1\]\:baz1\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='1'][data-qux='1'][data-fred='1'], + .fred1\:qux-\[1\]\:baz1\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='1'][data-qux='1'][data-fred='1'], + .fred1\:qux-\[1\]\:baz1\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='1'][data-qux='1'][data-fred='1'], + .fred1\:qux-\[1\]\:baz1\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='1'][data-qux='1'][data-fred='1'], + .fred1\:qux-\[1\]\:baz2\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='2'][data-qux='1'][data-fred='1'], + .fred1\:qux-\[1\]\:baz2\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='2'][data-qux='1'][data-fred='1'], + .fred1\:qux-\[1\]\:baz2\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='2'][data-qux='1'][data-fred='1'], + .fred1\:qux-\[1\]\:baz2\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='2'][data-qux='1'][data-fred='1'], + .fred2\:qux-\[2\]\:baz1\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='1'][data-qux='2'][data-fred='2'], + .fred2\:qux-\[2\]\:baz1\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='1'][data-qux='2'][data-fred='2'], + .fred2\:qux-\[2\]\:baz1\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='1'][data-qux='2'][data-fred='2'], + .fred2\:qux-\[2\]\:baz1\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='1'][data-qux='2'][data-fred='2'], + .fred2\:qux-\[2\]\:baz2\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='2'][data-qux='2'][data-fred='2'], + .fred2\:qux-\[2\]\:baz2\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='2'][data-qux='2'][data-fred='2'], + .fred2\:qux-\[2\]\:baz2\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='2'][data-qux='2'][data-fred='2'], + .fred2\:qux-\[2\]\:baz2\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='2'][data-qux='2'][data-fred='2'], + .fred2\:qux-\[1\]\:baz1\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='1'][data-qux='1'][data-fred='2'], + .fred2\:qux-\[1\]\:baz1\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='1'][data-qux='1'][data-fred='2'], + .fred2\:qux-\[1\]\:baz1\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='1'][data-qux='1'][data-fred='2'], + .fred2\:qux-\[1\]\:baz1\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='1'][data-qux='1'][data-fred='2'], + .fred2\:qux-\[1\]\:baz2\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='2'][data-qux='1'][data-fred='2'], + .fred2\:qux-\[1\]\:baz2\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='2'][data-qux='1'][data-fred='2'], + .fred2\:qux-\[1\]\:baz2\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='2'][data-qux='1'][data-fred='2'], + .fred2\:qux-\[1\]\:baz2\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='2'][data-qux='1'][data-fred='2'] { + padding: 0.25rem; + } + `) }) }) diff --git a/tests/basic-usage.test.js b/tests/basic-usage.test.js index 16c6ff771969..bde7df67d93d 100644 --- a/tests/basic-usage.test.js +++ b/tests/basic-usage.test.js @@ -1,134 +1,133 @@ import fs from 'fs' import path from 'path' -import { crosscheck, run, html, css, defaults } from './util/run' - -crosscheck(({ stable, oxide, engine }) => { - test('basic usage', () => { - let config = { - content: [ - { - raw: html` -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
{ backdrop-saturate-150 backdrop-sepia " - >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - } + >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ `, + }, + ], + 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( - fs.readFileSync(path.resolve(__dirname, './basic-usage.test.css'), 'utf8') - ) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss( + fs.readFileSync(path.resolve(__dirname, './basic-usage.test.css'), 'utf8') + ) }) +}) - test('all plugins are executed that match a candidate', () => { - let config = { - content: [{ raw: html`
` }], - theme: { - colors: { - green: { - light: 'green', - }, +test('all plugins are executed that match a candidate', () => { + let config = { + content: [{ raw: html`
` }], + theme: { + colors: { + green: { + light: 'green', }, }, - corePlugins: { preflight: false }, - } + }, + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; - let input = css` - @tailwind utilities; + .bg-green { + /* Empty on purpose */ + } + ` - .bg-green { - /* Empty on purpose */ + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .bg-green-light { + --tw-bg-opacity: 1; + background-color: rgb(0 128 0 / var(--tw-bg-opacity)); } - ` - - return run(input, config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .bg-green-light { - --tw-bg-opacity: 1; - background-color: rgb(0 128 0 / var(--tw-bg-opacity)); - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .bg-green-light { - background-color: green; - } - `) - }) + `) }) +}) - test('per-plugin colors with the same key can differ when using a custom colors object', () => { - let config = { - content: [ - { - raw: html` -
This should be green text on red background.
- `, +test('per-plugin colors with the same key can differ when using a custom colors object', () => { + let config = { + content: [ + { + raw: html` +
This should be green text on red background.
+ `, + }, + ], + theme: { + // colors & theme MUST be plain objects + // If they're functions here the test passes regardless + colors: { + theme: { + bg: 'red', + text: 'green', }, - ], - theme: { - // colors & theme MUST be plain objects - // If they're functions here the test passes regardless - colors: { + }, + extend: { + textColor: { theme: { - bg: 'red', - text: 'green', + DEFAULT: 'green', }, }, - extend: { - textColor: { - theme: { - DEFAULT: 'green', - }, - }, - backgroundColor: { - theme: { - DEFAULT: 'red', - }, + backgroundColor: { + theme: { + DEFAULT: 'red', }, }, }, - corePlugins: { preflight: false }, - } + }, + corePlugins: { preflight: false }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - return run(input, config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .bg-theme { - --tw-bg-opacity: 1; - background-color: rgb(255 0 0 / var(--tw-bg-opacity)); - } - .text-theme { - --tw-text-opacity: 1; - color: rgb(0 128 0 / var(--tw-text-opacity)); - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .bg-theme { - background-color: red; - } - .text-theme { - color: green; - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .bg-theme { + --tw-bg-opacity: 1; + background-color: rgb(255 0 0 / var(--tw-bg-opacity)); + } + .text-theme { + --tw-text-opacity: 1; + color: rgb(0 128 0 / var(--tw-text-opacity)); + } + `) }) +}) - test('default ring color can be a function', () => { - function color(variable) { - return function ({ opacityVariable, opacityValue }) { - if (opacityValue !== undefined) { - return `rgba(${variable}, ${opacityValue})` - } - if (opacityVariable !== undefined) { - return `rgba(${variable}, var(${opacityVariable}, 1))` - } - return `rgb(${variable})` +test('default ring color can be a function', () => { + function color(variable) { + return function ({ opacityVariable, opacityValue }) { + if (opacityValue !== undefined) { + return `rgba(${variable}, ${opacityValue})` + } + if (opacityVariable !== undefined) { + return `rgba(${variable}, var(${opacityVariable}, 1))` } + return `rgb(${variable})` } + } - let config = { - content: [ - { - raw: html`
`, - }, - ], + let config = { + content: [ + { + raw: html`
`, + }, + ], - theme: { - extend: { - ringColor: { - DEFAULT: color('var(--red)'), - }, + theme: { + extend: { + ringColor: { + DEFAULT: color('var(--red)'), }, }, - plugins: [], - corePlugins: { preflight: false }, - } + }, + plugins: [], + 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({ defaultRingColor: 'rgba(var(--red), 0.5)' })} - .ring { - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) - var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) - var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), - var(--tw-shadow, 0 0 #0000); - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults({ defaultRingColor: 'rgba(var(--red), 0.5)' })} + .ring { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) + var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) + var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); + } + `) }) +}) - it('falsy config values still work', () => { - let config = { - content: [{ raw: html`
` }], - theme: { - inset: { - 0: 0, - }, +it('falsy config values still work', () => { + let config = { + content: [{ raw: html`
` }], + theme: { + inset: { + 0: 0, }, - plugins: [], - corePlugins: { preflight: false }, - } + }, + plugins: [], + corePlugins: { preflight: false }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .inset-0 { - inset: 0; - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .inset-0 { + inset: 0; + } + `) }) +}) - it('shadows support values without a leading zero', () => { - let config = { - content: [{ raw: html`
` }], - theme: { - boxShadow: { - one: '0.5rem 0.5rem 0.5rem #0005', - two: '.5rem .5rem .5rem #0005', - }, +it('shadows support values without a leading zero', () => { + let config = { + content: [{ raw: html`
` }], + theme: { + boxShadow: { + one: '0.5rem 0.5rem 0.5rem #0005', + two: '.5rem .5rem .5rem #0005', }, - plugins: [], - corePlugins: { preflight: false }, - } + }, + plugins: [], + corePlugins: { preflight: false }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .shadow-one, - .shadow-two { - --tw-shadow: 0.5rem 0.5rem 0.5rem #0005; - --tw-shadow-colored: 0.5rem 0.5rem 0.5rem 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).toMatchFormattedCss(css` + .shadow-one, + .shadow-two { + --tw-shadow: 0.5rem 0.5rem 0.5rem #0005; + --tw-shadow-colored: 0.5rem 0.5rem 0.5rem var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); + } + `) }) +}) - it('can scan extremely long classes without crashing', () => { - let val = 'cols-' + '-a'.repeat(65536) - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } +it('can scan extremely long classes without crashing', () => { + let val = 'cols-' + '-a'.repeat(65536) + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css``) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css``) }) +}) + +it('does not produce duplicate output when seeing variants preceding a wildcard (*)', () => { + let config = { + content: [{ raw: html`underline focus:*` }], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind base; + @tailwind components; + @tailwind utilities; - it('does not produce duplicate output when seeing variants preceding a wildcard (*)', () => { - let config = { - content: [{ raw: html`underline focus:*` }], - corePlugins: { preflight: false }, + * { + color: red; } - let input = css` - @tailwind base; - @tailwind components; - @tailwind utilities; + .combined, + * { + text-align: center; + } + @layer base { * { - color: red; + color: blue; } .combined, * { - text-align: center; + color: red; } + } + ` - @layer base { - * { - color: blue; - } - - .combined, - * { - color: red; - } + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + * { + color: #00f; } - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - * { - color: #00f; - } - .combined, - * { - color: red; - } - ${defaults} - .underline { - text-decoration-line: underline; - } - * { - color: red; - } - .combined, - * { - text-align: center; - } - `) - }) + .combined, + * { + color: red; + } + ${defaults} + .underline { + text-decoration-line: underline; + } + * { + color: red; + } + .combined, + * { + text-align: center; + } + `) }) +}) - it('can parse box shadows with variables', () => { - let config = { - content: [{ raw: html`
` }], - theme: { - boxShadow: { - lg: 'var(--a, 0 35px 60px -15px rgba(0, 0, 0)), 0 0 1px rgb(0, 0, 0)', - }, +it('can parse box shadows with variables', () => { + let config = { + content: [{ raw: html`
` }], + theme: { + boxShadow: { + lg: 'var(--a, 0 35px 60px -15px rgba(0, 0, 0)), 0 0 1px rgb(0, 0, 0)', }, - corePlugins: { preflight: false }, - } + }, + corePlugins: { preflight: false }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .shadow-lg { - --tw-shadow: var(--a, 0 35px 60px -15px #000), 0 0 1px #000; - --tw-shadow-colored: 0 35px 60px -15px var(--tw-shadow-color), - 0 0 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).toMatchFormattedCss(css` + .shadow-lg { + --tw-shadow: var(--a, 0 35px 60px -15px #000), 0 0 1px #000; + --tw-shadow-colored: 0 35px 60px -15px var(--tw-shadow-color), + 0 0 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); + } + `) }) +}) - it('should generate styles using :not(.unknown-class) even if `.unknown-class` does not exist', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } +it('should generate styles using :not(.unknown-class) even if `.unknown-class` does not exist', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } - let input = css` - @tailwind components; + let input = css` + @tailwind components; - @layer components { - div:not(.unknown-class) { - color: red; - } + @layer components { + div:not(.unknown-class) { + color: red; } - ` + } + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - div:not(.unknown-class) { - color: red; - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + div:not(.unknown-class) { + color: red; + } + `) }) +}) - it('supports multiple backgrounds as arbitrary values even if only some are quoted', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - } +it('supports multiple backgrounds as arbitrary values even if only some are quoted', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .bg-\[url\(\'\/images\/one-two-three\.png\'\)\,linear-gradient\(to_right\,_\#eeeeee\,_\#000000\)\] { - background-image: url('/images/one-two-three.png'), linear-gradient(to right, #eee, #000); - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .bg-\[url\(\'\/images\/one-two-three\.png\'\)\,linear-gradient\(to_right\,_\#eeeeee\,_\#000000\)\] { + background-image: url('/images/one-two-three.png'), linear-gradient(to right, #eee, #000); + } + `) }) +}) - it('The "default" ring opacity is used by the default ring color when not using respectDefaultRingColorOpacity (1)', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } +it('The "default" ring opacity is used by the default ring color when not using respectDefaultRingColorOpacity (1)', () => { + let config = { + content: [{ raw: html`
` }], + 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).toMatchFormattedCss(css` - ${defaults({ defaultRingColor: '#3b82f680' })} - .ring { - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) - var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) - var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), - var(--tw-shadow, 0 0 #0000); - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults({ defaultRingColor: '#3b82f680' })} + .ring { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) + var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) + var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); + } + `) }) +}) - it('The "default" ring opacity is used by the default ring color when not using respectDefaultRingColorOpacity (2)', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - theme: { - ringOpacity: { - DEFAULT: 0.75, - }, +it('The "default" ring opacity is used by the default ring color when not using respectDefaultRingColorOpacity (2)', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + theme: { + ringOpacity: { + DEFAULT: 0.75, }, - } + }, + } - let input = css` - @tailwind base; - @tailwind utilities; - ` + let input = css` + @tailwind base; + @tailwind utilities; + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults({ defaultRingColor: '#3b82f6bf' })} - .ring { - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) - var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) - var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), - var(--tw-shadow, 0 0 #0000); - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults({ defaultRingColor: '#3b82f6bf' })} + .ring { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) + var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) + var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); + } + `) }) +}) - it('Customizing the default ring color uses the "default" opacity when not using respectDefaultRingColorOpacity (1)', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - theme: { - ringColor: { - DEFAULT: '#ff7f7f', - }, +it('Customizing the default ring color uses the "default" opacity when not using respectDefaultRingColorOpacity (1)', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + theme: { + ringColor: { + DEFAULT: '#ff7f7f', }, - } + }, + } - let input = css` - @tailwind base; - @tailwind utilities; - ` + let input = css` + @tailwind base; + @tailwind utilities; + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults({ defaultRingColor: '#ff7f7f80' })} - .ring { - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) - var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) - var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), - var(--tw-shadow, 0 0 #0000); - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults({ defaultRingColor: '#ff7f7f80' })} + .ring { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) + var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) + var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); + } + `) }) +}) - it('Customizing the default ring color uses the "default" opacity when not using respectDefaultRingColorOpacity (2)', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - theme: { - ringColor: { - DEFAULT: '#ff7f7f00', - }, +it('Customizing the default ring color uses the "default" opacity when not using respectDefaultRingColorOpacity (2)', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + theme: { + ringColor: { + DEFAULT: '#ff7f7f00', }, - } + }, + } - let input = css` - @tailwind base; - @tailwind utilities; - ` + let input = css` + @tailwind base; + @tailwind utilities; + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults({ defaultRingColor: '#ff7f7f80' })} - .ring { - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) - var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) - var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), - var(--tw-shadow, 0 0 #0000); - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults({ defaultRingColor: '#ff7f7f80' })} + .ring { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) + var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) + var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); + } + `) }) +}) - it('The "default" ring color ignores the default opacity when using respectDefaultRingColorOpacity (1)', () => { - let config = { - future: { respectDefaultRingColorOpacity: true }, - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } +it('The "default" ring color ignores the default opacity when using respectDefaultRingColorOpacity (1)', () => { + let config = { + future: { respectDefaultRingColorOpacity: true }, + content: [{ raw: html`
` }], + 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).toMatchFormattedCss(css` - ${defaults({ defaultRingColor: '#3b82f67f' })} - .ring { - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) - var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) - var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), - var(--tw-shadow, 0 0 #0000); - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults({ defaultRingColor: '#3b82f67f' })} + .ring { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) + var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) + var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); + } + `) }) +}) - it('The "default" ring color ignores the default opacity when using respectDefaultRingColorOpacity (2)', () => { - let config = { - future: { respectDefaultRingColorOpacity: true }, - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - theme: { - ringOpacity: { - DEFAULT: 0.75, - }, +it('The "default" ring color ignores the default opacity when using respectDefaultRingColorOpacity (2)', () => { + let config = { + future: { respectDefaultRingColorOpacity: true }, + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + theme: { + ringOpacity: { + DEFAULT: 0.75, }, - } + }, + } - let input = css` - @tailwind base; - @tailwind utilities; - ` + let input = css` + @tailwind base; + @tailwind utilities; + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults({ defaultRingColor: '#3b82f67f' })} - .ring { - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) - var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) - var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), - var(--tw-shadow, 0 0 #0000); - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults({ defaultRingColor: '#3b82f67f' })} + .ring { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) + var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) + var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); + } + `) }) +}) - it('Customizing the default ring color preserves its opacity when using respectDefaultRingColorOpacity (1)', () => { - let config = { - future: { respectDefaultRingColorOpacity: true }, - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - theme: { - ringColor: { - DEFAULT: '#ff7f7f', - }, +it('Customizing the default ring color preserves its opacity when using respectDefaultRingColorOpacity (1)', () => { + let config = { + future: { respectDefaultRingColorOpacity: true }, + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + theme: { + ringColor: { + DEFAULT: '#ff7f7f', }, - } + }, + } - let input = css` - @tailwind base; - @tailwind utilities; - ` + let input = css` + @tailwind base; + @tailwind utilities; + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults({ defaultRingColor: '#ff7f7f' })} - .ring { - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) - var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) - var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), - var(--tw-shadow, 0 0 #0000); - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults({ defaultRingColor: '#ff7f7f' })} + .ring { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) + var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) + var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); + } + `) }) +}) - it('Customizing the default ring color preserves its opacity when using respectDefaultRingColorOpacity (2)', () => { - let config = { - future: { respectDefaultRingColorOpacity: true }, - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - theme: { - ringColor: { - DEFAULT: '#ff7f7f00', - }, +it('Customizing the default ring color preserves its opacity when using respectDefaultRingColorOpacity (2)', () => { + let config = { + future: { respectDefaultRingColorOpacity: true }, + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + theme: { + ringColor: { + DEFAULT: '#ff7f7f00', }, - } + }, + } - let input = css` - @tailwind base; - @tailwind utilities; - ` + let input = css` + @tailwind base; + @tailwind utilities; + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults({ defaultRingColor: '#ff7f7f00' })} - .ring { - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) - var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) - var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), - var(--tw-shadow, 0 0 #0000); - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults({ defaultRingColor: '#ff7f7f00' })} + .ring { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) + var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) + var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); + } + `) }) +}) - it('A bare ring-opacity utility is not supported when not using respectDefaultRingColorOpacity', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - theme: { - ringOpacity: { - DEFAULT: '0.33', - }, +it('A bare ring-opacity utility is not supported when not using respectDefaultRingColorOpacity', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + theme: { + ringOpacity: { + DEFAULT: '0.33', }, - } + }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css``) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css``) }) +}) - test('A bare ring-opacity utility is supported when using respectDefaultRingColorOpacity', () => { - let config = { - future: { respectDefaultRingColorOpacity: true }, - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - theme: { - ringOpacity: { - DEFAULT: '0.33', - }, +test('A bare ring-opacity utility is supported when using respectDefaultRingColorOpacity', () => { + let config = { + future: { respectDefaultRingColorOpacity: true }, + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + theme: { + ringOpacity: { + DEFAULT: '0.33', }, - } + }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - return run(input, config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .ring-opacity { - --tw-ring-opacity: 0.33; - } - `) - // The opacity plugins are disabled by default in the `oxide` engine - oxide.expect(result.css).toMatchFormattedCss(css``) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .ring-opacity { + --tw-ring-opacity: 0.33; + } + `) }) +}) - it('Ring color utilities are generated when using respectDefaultRingColorOpacity', () => { - let config = { - future: { respectDefaultRingColorOpacity: true }, - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } +it('Ring color utilities are generated when using respectDefaultRingColorOpacity', () => { + let config = { + future: { respectDefaultRingColorOpacity: true }, + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - return run(input, config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .ring { - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) - var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) - var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), - var(--tw-shadow, 0 0 #0000); - } - .ring-blue-500 { - --tw-ring-opacity: 1; - --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity)); - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .ring { - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) - var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) - var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), - var(--tw-shadow, 0 0 #0000); - } - .ring-blue-500 { - --tw-ring-color: #3b82f6; - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .ring { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) + var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) + var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); + } + .ring-blue-500 { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity)); + } + `) }) +}) - oxide.test.todo('should not crash when group names contain special characters') - stable.test('should not crash when group names contain special characters', () => { - let config = { - future: { respectDefaultRingColorOpacity: true }, - content: [ - { - raw: '
', - }, - ], - corePlugins: { preflight: false }, - } +test('should not crash when group names contain special characters', () => { + let config = { + future: { respectDefaultRingColorOpacity: true }, + content: [ + { + raw: '
', + }, + ], + corePlugins: { preflight: false }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .group\/\$\{id\}:hover .group-hover\/\$\{id\}\:visible { - visibility: visible; - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .group\/\$\{id\}:hover .group-hover\/\$\{id\}\:visible { + visibility: visible; + } + `) }) +}) - it('should not crash when matching variants where utility classes are doubled up', () => { - let config = { - content: [ - { - raw: '
', - }, - ], - } +it('should not crash when matching variants where utility classes are doubled up', () => { + let config = { + content: [ + { + raw: '
', + }, + ], + } - let input = css` - @tailwind utilities; - @layer utilities { - .foo.foo { - text-decoration-line: underline; - } + let input = css` + @tailwind utilities; + @layer utilities { + .foo.foo { + text-decoration-line: underline; } - ` + } + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .hover\:foo:hover.hover\:foo:hover { - text-decoration-line: underline; - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .hover\:foo:hover.hover\:foo:hover { + text-decoration-line: underline; + } + `) }) +}) - test('detects quoted arbitrary values containing a slash', async () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - } +test('detects quoted arbitrary values containing a slash', async () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - let result = await run(input, config) + let result = await run(input, config) - oxide.expect(result.css).toMatchFormattedCss(css` - .group[href^='/'] .group-\[\[href\^\=\'\/\'\]\]\:hidden { - display: none; - } - `) + expect(result.css).toMatchFormattedCss(css` + .group[href^='/'] .group-\[\[href\^\=\'\/\'\]\]\:hidden { + display: none; + } + `) +}) - stable.expect(result.css).toMatchFormattedCss(css` - .group[href^='/'] .group-\[\[href\^\=\'\/\'\]\]\:hidden { - display: none; - } - `) - }) +test('handled quoted arbitrary values containing escaped spaces', async () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + } - test('handled quoted arbitrary values containing escaped spaces', async () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], + let input = css` + @tailwind utilities; + ` + + let result = await run(input, config) + + expect(result.css).toMatchFormattedCss(css` + .group[href^=' bar'] .group-\[\[href\^\=\'_bar\'\]\]\:hidden { + display: none; } + `) +}) - let input = css` - @tailwind utilities; - ` +test('detects quoted arbitrary values containing a slash', async () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + } - let result = await run(input, config) + let input = css` + @tailwind utilities; + ` - oxide.expect(result.css).toMatchFormattedCss(css` - .group[href^=' bar'] .group-\[\[href\^\=\'_bar\'\]\]\:hidden { - display: none; - } - `) + let result = await run(input, config) - stable.expect(result.css).toMatchFormattedCss(css` - .group[href^=' bar'] .group-\[\[href\^\=\'_bar\'\]\]\:hidden { + expect(result.css).toMatchFormattedCss( + css` + .group[href^='/'] .group-\[\[href\^\=\'\/\'\]\]\:hidden { display: none; } - `) - }) - - test('detects quoted arbitrary values containing a slash', async () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - } - - let input = css` - @tailwind utilities; ` + ) +}) - let result = await run(input, config) +test('handled quoted arbitrary values containing escaped spaces', async () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + } - expect(result.css).toMatchFormattedCss( - engine.oxide - ? css` - .group[href^='/'] .group-\[\[href\^\=\'\/\'\]\]\:hidden { - display: none; - } - ` - : css` - .group[href^='/'] .group-\[\[href\^\=\'\/\'\]\]\:hidden { - display: none; - } - ` - ) - }) + let input = css` + @tailwind utilities; + ` - test('handled quoted arbitrary values containing escaped spaces', async () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - } + let result = await run(input, config) - let input = css` - @tailwind utilities; + expect(result.css).toMatchFormattedCss( + css` + .group[href^=' bar'] .group-\[\[href\^\=\'_bar\'\]\]\:hidden { + display: none; + } ` + ) +}) - let result = await run(input, config) - - expect(result.css).toMatchFormattedCss( - engine.oxide - ? css` - .group[href^=' bar'] .group-\[\[href\^\=\'_bar\'\]\]\:hidden { - display: none; - } - ` - : css` - .group[href^=' bar'] .group-\[\[href\^\=\'_bar\'\]\]\:hidden { - display: none; - } - ` - ) - }) - - test('Skips classes inside :not() when nested inside an at-rule', async () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - function ({ addUtilities }) { - addUtilities({ - '.hand:not(.disabled)': { - '@supports (cursor: pointer)': { - cursor: 'pointer', - }, +test('Skips classes inside :not() when nested inside an at-rule', async () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + function ({ addUtilities }) { + addUtilities({ + '.hand:not(.disabled)': { + '@supports (cursor: pointer)': { + cursor: 'pointer', }, - }) - }, - ], - } + }, + }) + }, + ], + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - // We didn't find the hand class therefore - // nothing should be generated - let result = await run(input, config) + // We didn't find the hand class therefore + // nothing should be generated + let result = await run(input, config) - expect(result.css).toMatchFormattedCss(css``) - }) + expect(result.css).toMatchFormattedCss(css``) }) test('Irrelevant rules are removed when applying variants', async () => { diff --git a/tests/blocklist.test.js b/tests/blocklist.test.js index 3642aa376e02..e9915350f9e7 100644 --- a/tests/blocklist.test.js +++ b/tests/blocklist.test.js @@ -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`
`, - }, - ], - blocklist: ['font', 'uppercase', 'hover:text-sm', 'bg-red-500/50', 'my-custom-class'], +it('can block classes matched literally', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + 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; - } - .my-custom-class { - color: red; + @media (min-width: 640px) { + .sm\:hover\:text-sm:hover { + font-size: 0.875rem; + line-height: 1.25rem; } - @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`
`, - }, - ], - blocklist: ['my-custom-class'], - } +it('can block classes inside @layer', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + blocklist: ['my-custom-class'], + } - let input = css` - @tailwind utilities; - @layer utilities { - .my-custom-class { - color: red; - } + 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`
` }], - 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)); - } + 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`
` }], + blocklist: [/^bg-\[[^]+\]$/], + } + + let result = await run('@tailwind utilities', config) + + 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; + } + `) + + expect().toHaveBeenWarnedWith(['blocklist-invalid']) +}) + +it('can block classes generated by the safelist', () => { + let config = { + content: [{ raw: html`
` }], + safelist: [{ pattern: /^bg-red-(400|500)$/ }], + blocklist: ['bg-red-500'], + } - oxide.expect(result.css).toMatchFormattedCss(css` - .bg-\[\#f00d1e\] { - background-color: #f00d1e; + 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; } `) - - expect().toHaveBeenWarnedWith(['blocklist-invalid']) - }) - - it('can block classes generated by the safelist', () => { - let config = { - content: [{ raw: html`
` }], - safelist: [{ pattern: /^bg-red-(400|500)$/ }], - blocklist: ['bg-red-500'], - } - - 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; - } - `) - }) }) }) diff --git a/tests/collapse-adjacent-rules.test.js b/tests/collapse-adjacent-rules.test.js index 4dbaab769ed4..69c471686536 100644 --- a/tests/collapse-adjacent-rules.test.js +++ b/tests/collapse-adjacent-rules.test.js @@ -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` -
-
-
-
-
-
-
-
-
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - theme: {}, - plugins: [ - function ({ addVariant }) { - addVariant('foo-bar', '@supports (foo: bar)') - }, - ], +test('collapse adjacent rules', () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+
+
+
+
+
+
+
+
+ `, + }, + ], + 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'); + } + @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: '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; } + ` - let input = css` - @tailwind base; + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` @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'); + } + ${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; + .font-bold { + font-weight: 700; } .foo, .bar { + color: #000; 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) => { - 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; - } - .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 { - font-weight: 700; + } + @media (min-width: 640px) { + .sm\:text-center { + text-align: center; } - .foo, - .bar { - color: #000; + .sm\:font-bold { font-weight: 700; } - @supports (foo: bar) { - .some-apply-thing { - color: #000; - font-weight: 700; - } - } - @media (min-width: 768px) { - .some-apply-thing { - color: #000; - font-weight: 700; - } - } - @supports (foo: bar) { - @media (min-width: 768px) { - .some-apply-thing { - color: #000; - font-weight: 700; - } - } + } + @media (min-width: 768px) { + .md\:text-center { + text-align: center; } - @media (min-width: 640px) { - .sm\:text-center { - text-align: center; - } - .sm\:font-bold { - font-weight: 700; - } + .md\: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; } - @media (min-width: 1024px) { - .lg\:text-center { - text-align: center; - } - .lg\:font-bold { - font-weight: 700; - } + .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'; + `) }) }) diff --git a/tests/collapse-duplicate-declarations.test.js b/tests/collapse-duplicate-declarations.test.js index 128eb9bef475..dc8834b74b3c 100644 --- a/tests/collapse-duplicate-declarations.test.js +++ b/tests/collapse-duplicate-declarations.test.js @@ -1,177 +1,175 @@ -import { crosscheck, run, html, css } from './util/run' - -crosscheck(() => { - it('should collapse duplicate declarations with the same units (px)', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - plugins: [], +import { run, html, css } from './util/run' + +it('should collapse duplicate declarations with the same units (px)', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + plugins: [], + } + + let input = css` + @tailwind utilities; + + @layer utilities { + .example { + height: 100px; + height: 200px; + } } + ` - let input = css` - @tailwind utilities; - - @layer utilities { - .example { - height: 100px; - height: 200px; - } + 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 { - height: 200px; - } - `) - }) + `) }) +}) - it('should collapse duplicate declarations with the same units (no unit)', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - plugins: [], - } +it('should collapse duplicate declarations with the same units (no unit)', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + plugins: [], + } - let input = css` - @tailwind utilities; + let input = css` + @tailwind utilities; - @layer utilities { - .example { - line-height: 3; - line-height: 2; - } + @layer utilities { + .example { + line-height: 3; + line-height: 2; } - ` - - 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`
` }], - corePlugins: { preflight: false }, - plugins: [], } + ` - let input = css` - @tailwind utilities; - - @layer utilities { - .example { - height: 100px; - height: 50%; - } + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .example { + line-height: 2; } - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .example { - height: 100px; - height: 50%; - } - `) - }) + `) }) +}) + +it('should not collapse duplicate declarations with the different units', () => { + let config = { + content: [{ raw: html`
` }], + 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`
` }], - 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%; - height: 20vh; - height: 200px; - height: 100%; - height: 30vh; - } + 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: 200px; - height: 100%; - height: 30vh; - } - `) - }) + `) }) +}) - it('should collapse the duplicate declarations with the exact same value', () => { - let config = { - content: [{ raw: html`
` }], - 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`
` }], + 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: var(--value); - color: blue; - height: var(--value); - } + ` + + 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 { - color: #00f; - height: var(--value); - } - `) - }) + `) }) +}) - it('should work on a real world example', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - plugins: [], +it('should collapse the duplicate declarations with the exact same value', () => { + let config = { + content: [{ raw: html`
` }], + 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; + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .example { + color: #00f; + height: var(--value); + } + `) + }) +}) - @layer utilities { - .h-available { - height: 100%; - height: 100vh; - height: -webkit-fill-available; - } +it('should work on a real world example', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + plugins: [], + } + + 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; - } - `) - }) + `) }) }) diff --git a/tests/color-opacity-modifiers.test.js b/tests/color-opacity-modifiers.test.js index 8da4d38c2a66..a4600a495bdb 100644 --- a/tests/color-opacity-modifiers.test.js +++ b/tests/color-opacity-modifiers.test.js @@ -1,264 +1,234 @@ -import { crosscheck, run, html, css } from './util/run' - -crosscheck(({ stable, oxide }) => { - test('basic color opacity modifier', async () => { - let config = { - content: [{ raw: html`
` }], - } - - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .bg-red-500\/50 { - background-color: #ef444480; - } - `) - }) +import { run, html, css } from './util/run' + +test('basic color opacity modifier', async () => { + let config = { + content: [{ raw: html`
` }], + } + + 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`
` }], - theme: { - extend: { - colors: { - 'red-500/50': '#ff0000', - }, +test('colors with slashes are matched first', async () => { + let config = { + content: [{ raw: html`
` }], + 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`
` }], - } - - 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`
` }], + } + + 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`
` }], - } +test('missing alpha generates nothing', async () => { + let config = { + content: [{ raw: html`
` }], + } - 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`
` }], - 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`
` }], + 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`
` }], - theme: { - colors: { - blue: ({ opacityValue }) => { - return `rgba(var(--colors-blue), ${opacityValue})` - }, +test('function colors are supported', async () => { + let config = { + content: [{ raw: html`
` }], + 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` -
-
-
- `, - }, - ], - theme: { - extend: { - fill: (theme) => theme('colors'), - }, +test('utilities that support any type are supported', async () => { + let config = { + content: [ + { + raw: html` +
+
+
+ `, }, - 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; - } - `) - }) + ], + 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; + } + `) }) +}) - test('opacity modifier in combination with partial custom properties', async () => { - let config = { - content: [ - { - raw: html` -
-
-
-
-
-
-
- `, - }, - ], - 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` +
+
+
+
+
+
+
+ `, + }, + ], + 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); + } + `) }) }) diff --git a/tests/color.test.js b/tests/color.test.js index be1d85924562..659a2d045b51 100644 --- a/tests/color.test.js +++ b/tests/color.test.js @@ -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) - }) +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) - }) + 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) - }) +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) }) }) diff --git a/tests/combined-selectors.test.js b/tests/combined-selectors.test.js index 51aa0a8ce2b9..b3e8a333f320 100644 --- a/tests/combined-selectors.test.js +++ b/tests/combined-selectors.test.js @@ -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`
` }], - corePlugins: { preflight: false }, - } +it('should generate the partial selector, if only a partial is used (base layer)', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } - let input = css` - @tailwind base; + let input = css` + @tailwind base; - @layer base { - :root { - font-weight: bold; - } + @layer base { + :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; - } - ${defaults} - `) - }) + 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`
` }], - corePlugins: { preflight: false }, - } +it('should generate the partial selector, if only a partial is used (utilities layer)', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } - let input = css` - @tailwind utilities; + let input = css` + @tailwind utilities; - @layer utilities { - :root { - font-weight: bold; - } + @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; + } + `) }) }) diff --git a/tests/configurePlugins.test.js b/tests/configurePlugins.test.js index 367ab223fd91..490cddc0357a 100644 --- a/tests/configurePlugins.test.js +++ b/tests/configurePlugins.test.js @@ -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']) }) diff --git a/tests/containerPlugin.test.js b/tests/containerPlugin.test.js index 428a807d0f38..4b334e90c25d 100644 --- a/tests/containerPlugin.test.js +++ b/tests/containerPlugin.test.js @@ -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`
` }] } +test('options are not required', () => { + let config = { content: [{ raw: html`
` }] } - 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%; - } - @media (min-width: 640px) { - .container { - max-width: 640px; - } + 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('screens can be passed explicitly', () => { - let config = { - content: [{ raw: html`
` }], - theme: { - container: { - screens: ['400px', '500px'], - }, +test('screens can be passed explicitly', () => { + let config = { + content: [{ raw: html`
` }], + theme: { + container: { + screens: ['400px', '500px'], }, - } + }, + } - 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: 400px) { .container { - width: 100%; + max-width: 400px; } - @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('screens are ordered ascending by min-width', () => { - let config = { - content: [{ raw: html`
` }], - theme: { - container: { - screens: ['500px', '400px'], - }, +test('screens are ordered ascending by min-width', () => { + let config = { + content: [{ raw: html`
` }], + theme: { + container: { + screens: ['500px', '400px'], }, - } + }, + } - 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: 400px) { .container { - width: 100%; + max-width: 400px; } - @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('screens are deduplicated by min-width', () => { - let config = { - content: [{ raw: html`
` }], - theme: { - container: { - screens: { - sm: '576px', - md: '768px', - 'sm-only': { min: '576px', max: '767px' }, - }, +test('screens are deduplicated by min-width', () => { + let config = { + content: [{ raw: html`
` }], + 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 { - width: 100%; - } - @media (min-width: 576px) { - .container { - max-width: 576px; - } + max-width: 576px; } - @media (min-width: 768px) { - .container { - max-width: 768px; - } + } + @media (min-width: 768px) { + .container { + max-width: 768px; } - `) - }) + } + `) }) +}) - test('the container can be centered by default', () => { - let config = { - content: [{ raw: html`
` }], - theme: { - container: { - center: true, - }, +test('the container can be centered by default', () => { + let config = { + content: [{ raw: html`
` }], + theme: { + container: { + center: true, }, - } + }, + } - 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%; + margin-left: auto; + margin-right: auto; + } + @media (min-width: 640px) { .container { - width: 100%; - margin-left: auto; - margin-right: auto; - } - @media (min-width: 640px) { - .container { - max-width: 640px; - } + 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('horizontal padding can be included by default', () => { - let config = { - content: [{ raw: html`
` }], - theme: { - container: { - padding: '2rem', - }, +test('horizontal padding can be included by default', () => { + let config = { + content: [{ raw: html`
` }], + 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 { - width: 100%; - padding-left: 2rem; - padding-right: 2rem; + max-width: 640px; } - @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`
` }], - 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('responsive horizontal padding can be included by default', () => { + let config = { + content: [{ raw: html`
` }], + 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%; - padding-left: 1rem; - padding-right: 1rem; - } - @media (min-width: 576px) { - .container { - max-width: 576px; - padding-left: 2rem; - padding-right: 2rem; - } + max-width: 576px; + padding-left: 2rem; + padding-right: 2rem; } - @media (min-width: 768px) { - .container { - max-width: 768px; - } + } + @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: 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: 1200px) { + .container { + max-width: 1200px; + padding-left: 5rem; + padding-right: 5rem; } - `) - }) + } + `) }) +}) - test('setting all options at once', () => { - let config = { - content: [{ raw: html`
` }], - theme: { - container: { - screens: ['400px', '500px'], - center: true, - padding: '2rem', - }, +test('setting all options at once', () => { + let config = { + content: [{ raw: html`
` }], + theme: { + container: { + screens: ['400px', '500px'], + center: true, + 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%; + margin-left: auto; + margin-right: auto; + padding-left: 2rem; + padding-right: 2rem; + } + @media (min-width: 400px) { .container { - width: 100%; - margin-left: auto; - margin-right: auto; - padding-left: 2rem; - padding-right: 2rem; + max-width: 400px; } - @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`
` }], - theme: { - container: { - screens: ['400px', '500px'], - }, +test('container can use variants', () => { + let config = { + content: [{ raw: html`
` }], + 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%; - } - @media (min-width: 400px) { - .lg\:hover\:container:hover { - max-width: 400px; - } + 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; } } - `) - }) + } + `) }) }) diff --git a/tests/context-reuse.test.js b/tests/context-reuse.test.js index 4ead43fd4513..7cd006cde4a8 100644 --- a/tests/context-reuse.test.js +++ b/tests/context-reuse.test.js @@ -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)};`) +}) - await fs.promises.writeFile(configPath, `module.exports = ${JSON.stringify(config)};`) - }) +afterEach(async () => { + await fs.promises.unlink(configPath) +}) - 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) }) - 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`), - - // 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)); - } - `) + // The content files don't have any utilities in them so this should be empty + expect(results[0].css).toMatchFormattedCss(css``) - expect(results[2].css).toMatchFormattedCss(css` - body { - --tw-text-opacity: 1; - color: rgb(248 113 113 / var(--tw-text-opacity)); - } - `) + // 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)); + } + `) - expect(results[3].css).toMatchFormattedCss(css` - body { - margin-bottom: 1rem; - } - `) + expect(results[2].css).toMatchFormattedCss(css` + body { + --tw-text-opacity: 1; + color: rgb(248 113 113 / var(--tw-text-opacity)); + } + `) - // 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'), - ]) + expect(results[3].css).toMatchFormattedCss(css` + body { + margin-bottom: 1rem; + } + `) - // @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')]) + // 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'), + ]) - expect(dependencies[2]).toEqual([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[3]).toEqual([path.resolve(__dirname, 'context-reuse.tailwind.config.js')]) + expect(dependencies[2]).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) - }) + expect(dependencies[3]).toEqual([path.resolve(__dirname, 'context-reuse.tailwind.config.js')]) - 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; - } - } - ` + // And none of this should have resulted in multiple contexts being created + expect(sharedState.contextSourcesMap.size).toBe(1) +}) - result = await run(input, configPath, `id=1`) +test('updates layers when any CSS containing @tailwind directives changes', async () => { + let result - 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`) + 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: 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: #00f; + } + `) }) diff --git a/tests/custom-extractors.test.js b/tests/custom-extractors.test.js index 089084e77a41..f5933b7a595e 100644 --- a/tests/custom-extractors.test.js +++ b/tests/custom-extractors.test.js @@ -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) - }) + 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, - }, + 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('extract function') - stable.test('extract function', () => { - let config = { - content: { - files: [{ raw: sharedHtml }], - extract: customExtractor, - }, - } + test('extract function', () => { + let config = { + content: { + files: [{ raw: sharedHtml }], + extract: 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('raw content with extension') - stable.test('raw content with extension', () => { - let config = { - content: { - files: [ - { - raw: sharedHtml, - extension: 'html', - }, - ], - extract: { - html: () => ['invisible'], + test('raw content with extension', () => { + let config = { + content: { + files: [ + { + raw: sharedHtml, + extension: 'html', }, + ], + extract: { + html: () => ['invisible'], }, - corePlugins: { preflight: false }, - } + }, + 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(css` + .invisible { + visibility: hidden; + } + `) }) }) +}) - describe('legacy', () => { - oxide.test.todo('defaultExtractor') - stable.test('defaultExtractor', () => { - let config = { - content: { - files: [{ raw: sharedHtml }], - options: { - defaultExtractor: customExtractor, - }, +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) - }) + 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('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) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(expected) }) }) }) diff --git a/tests/custom-plugins.test.js b/tests/custom-plugins.test.js index d91843199b5c..0f45e98235f6 100644 --- a/tests/custom-plugins.test.js +++ b/tests/custom-plugins.test.js @@ -1,19 +1,56 @@ import createPlugin from '../src/public/create-plugin' -import { crosscheck, run, html, css, defaults } from './util/run' +import { run, html, css, defaults } from './util/run' -crosscheck(() => { - test('plugins can create utilities with object syntax', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - plugins: [ - function ({ addUtilities }) { - addUtilities({ +test('plugins can create utilities with object syntax', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + plugins: [ + function ({ addUtilities }) { + addUtilities({ + '.custom-object-fill': { + 'object-fit': 'fill', + }, + '.custom-object-contain': { + 'object-fit': 'contain', + }, + '.custom-object-cover': { + 'object-fit': 'cover', + }, + }) + }, + ], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .custom-object-fill { + object-fit: fill; + } + .custom-object-contain { + object-fit: contain; + } + .custom-object-cover { + object-fit: cover; + } + `) + }) +}) + +test('plugins can create utilities with arrays of objects', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + plugins: [ + function ({ addUtilities }) { + addUtilities([ + { '.custom-object-fill': { 'object-fit': 'fill', }, @@ -23,400 +60,413 @@ crosscheck(() => { '.custom-object-cover': { 'object-fit': 'cover', }, - }) - }, - ], - } + }, + ]) + }, + ], + } - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .custom-object-fill { - object-fit: fill; - } - .custom-object-contain { - object-fit: contain; - } - .custom-object-cover { - object-fit: cover; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .custom-object-fill { + object-fit: fill; + } + .custom-object-contain { + object-fit: contain; + } + .custom-object-cover { + object-fit: cover; + } + `) }) +}) - test('plugins can create utilities with arrays of objects', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - plugins: [ - function ({ addUtilities }) { - addUtilities([ - { - '.custom-object-fill': { - 'object-fit': 'fill', - }, - '.custom-object-contain': { - 'object-fit': 'contain', - }, - '.custom-object-cover': { - 'object-fit': 'cover', - }, +test('plugins can create utilities with raw PostCSS nodes', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + plugins: [ + function ({ addUtilities, postcss }) { + addUtilities([ + postcss.rule({ selector: '.custom-object-fill' }).append([ + postcss.decl({ + prop: 'object-fit', + value: 'fill', + }), + ]), + postcss.rule({ selector: '.custom-object-contain' }).append([ + postcss.decl({ + prop: 'object-fit', + value: 'contain', + }), + ]), + postcss.rule({ selector: '.custom-object-cover' }).append([ + postcss.decl({ + prop: 'object-fit', + value: 'cover', + }), + ]), + ]) + }, + ], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .custom-object-fill { + object-fit: fill; + } + .custom-object-contain { + object-fit: contain; + } + .custom-object-cover { + object-fit: cover; + } + `) + }) +}) + +test('plugins can create utilities with mixed object styles and PostCSS nodes', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + plugins: [ + function ({ addUtilities, postcss }) { + addUtilities([ + { + '.custom-object-fill': { + objectFit: 'fill', }, - ]) - }, - ], - } + }, + postcss.rule({ selector: '.custom-object-contain' }).append([ + postcss.decl({ + prop: 'object-fit', + value: 'contain', + }), + ]), + postcss.rule({ selector: '.custom-object-cover' }).append([ + postcss.decl({ + prop: 'object-fit', + value: 'cover', + }), + ]), + ]) + }, + ], + } - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .custom-object-fill { - object-fit: fill; - } - .custom-object-contain { - object-fit: contain; - } - .custom-object-cover { - object-fit: cover; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .custom-object-fill { + object-fit: fill; + } + .custom-object-contain { + object-fit: contain; + } + .custom-object-cover { + object-fit: cover; + } + `) }) +}) - test('plugins can create utilities with raw PostCSS nodes', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - plugins: [ - function ({ addUtilities, postcss }) { - addUtilities([ - postcss.rule({ selector: '.custom-object-fill' }).append([ - postcss.decl({ - prop: 'object-fit', - value: 'fill', - }), - ]), - postcss.rule({ selector: '.custom-object-contain' }).append([ - postcss.decl({ - prop: 'object-fit', - value: 'contain', - }), - ]), - postcss.rule({ selector: '.custom-object-cover' }).append([ - postcss.decl({ - prop: 'object-fit', - value: 'cover', - }), - ]), - ]) - }, - ], - } +test('plugins can create components with object syntax', () => { + let config = { + content: [ + { + raw: html``, + }, + ], + plugins: [ + function ({ addComponents }) { + addComponents({ + '.btn-blue': { + backgroundColor: 'blue', + color: 'white', + padding: '.5rem 1rem', + borderRadius: '.25rem', + }, + '.btn-blue:hover': { + backgroundColor: 'darkblue', + }, + }) + }, + ], + } + + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .btn-blue { + color: #fff; + background-color: #00f; + border-radius: 0.25rem; + padding: 0.5rem 1rem; + } + .btn-blue:hover { + background-color: #00008b; + } + `) + }) +}) - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .custom-object-fill { - object-fit: fill; - } - .custom-object-contain { - object-fit: contain; - } - .custom-object-cover { - object-fit: cover; - } - `) - }) +test('plugins can add base styles with object syntax', () => { + let config = { + content: [ + { + raw: html``, + }, + ], + plugins: [ + function ({ addBase }) { + addBase({ + img: { + maxWidth: '100%', + }, + button: { + fontFamily: 'inherit', + }, + }) + }, + ], + corePlugins: { preflight: false }, + } + + return run('@tailwind base', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + img { + max-width: 100%; + } + button { + font-family: inherit; + } + ${defaults} + `) }) +}) - test('plugins can create utilities with mixed object styles and PostCSS nodes', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - plugins: [ - function ({ addUtilities, postcss }) { - addUtilities([ - { - '.custom-object-fill': { - objectFit: 'fill', - }, - }, - postcss.rule({ selector: '.custom-object-contain' }).append([ - postcss.decl({ - prop: 'object-fit', - value: 'contain', - }), - ]), - postcss.rule({ selector: '.custom-object-cover' }).append([ - postcss.decl({ - prop: 'object-fit', - value: 'cover', - }), - ]), - ]) - }, - ], - } +test('plugins can add base styles with raw PostCSS nodes', () => { + let config = { + content: [ + { + raw: html``, + }, + ], + plugins: [ + function ({ addBase, postcss }) { + addBase([ + postcss.rule({ selector: 'img' }).append([ + postcss.decl({ + prop: 'max-width', + value: '100%', + }), + ]), + postcss.rule({ selector: 'button' }).append([ + postcss.decl({ + prop: 'font-family', + value: 'inherit', + }), + ]), + ]) + }, + ], + corePlugins: { preflight: false }, + } + + return run('@tailwind base', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + img { + max-width: 100%; + } + button { + font-family: inherit; + } + ${defaults} + `) + }) +}) - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .custom-object-fill { - object-fit: fill; - } - .custom-object-contain { - object-fit: contain; - } - .custom-object-cover { - object-fit: cover; - } - `) - }) +test('plugins can create components with raw PostCSS nodes', () => { + let config = { + content: [ + { + raw: html``, + }, + ], + plugins: [ + function ({ addComponents, postcss }) { + addComponents([ + postcss.rule({ selector: '.btn-blue' }).append([ + postcss.decl({ + prop: 'background-color', + value: 'blue', + }), + postcss.decl({ + prop: 'color', + value: 'white', + }), + postcss.decl({ + prop: 'padding', + value: '.5rem 1rem', + }), + postcss.decl({ + prop: 'border-radius', + value: '.25rem', + }), + ]), + postcss.rule({ selector: '.btn-blue:hover' }).append([ + postcss.decl({ + prop: 'background-color', + value: 'darkblue', + }), + ]), + ]) + }, + ], + } + + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .btn-blue { + color: #fff; + background-color: #00f; + border-radius: 0.25rem; + padding: 0.5rem 1rem; + } + .btn-blue:hover { + background-color: #00008b; + } + `) }) +}) - test('plugins can create components with object syntax', () => { - let config = { - content: [ - { - raw: html``, - }, - ], - plugins: [ - function ({ addComponents }) { - addComponents({ - '.btn-blue': { - backgroundColor: 'blue', - color: 'white', - padding: '.5rem 1rem', - borderRadius: '.25rem', - }, +test('plugins can create components with mixed object styles and raw PostCSS nodes', () => { + let config = { + content: [ + { + raw: html``, + }, + ], + plugins: [ + function ({ addComponents, postcss }) { + addComponents([ + postcss.rule({ selector: '.btn-blue' }).append([ + postcss.decl({ + prop: 'background-color', + value: 'blue', + }), + postcss.decl({ + prop: 'color', + value: 'white', + }), + postcss.decl({ + prop: 'padding', + value: '.5rem 1rem', + }), + postcss.decl({ + prop: 'border-radius', + value: '.25rem', + }), + ]), + { '.btn-blue:hover': { backgroundColor: 'darkblue', }, - }) - }, - ], - } - - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .btn-blue { - color: #fff; - background-color: #00f; - border-radius: 0.25rem; - padding: 0.5rem 1rem; - } - .btn-blue:hover { - background-color: #00008b; - } - `) - }) + }, + ]) + }, + ], + } + + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .btn-blue { + color: #fff; + background-color: #00f; + border-radius: 0.25rem; + padding: 0.5rem 1rem; + } + .btn-blue:hover { + background-color: #00008b; + } + `) }) +}) - test('plugins can add base styles with object syntax', () => { - let config = { - content: [ - { - raw: html``, - }, - ], - plugins: [ - function ({ addBase }) { - addBase({ - img: { - maxWidth: '100%', +test('plugins can create components with media queries with object syntax', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + plugins: [ + function ({ addComponents }) { + addComponents({ + '.custom-container': { + width: '100%', + }, + '@media (min-width: 100px)': { + '.custom-container': { + maxWidth: '100px', }, - button: { - fontFamily: 'inherit', + }, + '@media (min-width: 200px)': { + '.custom-container': { + maxWidth: '200px', }, - }) - }, - ], - corePlugins: { preflight: false }, - } - - return run('@tailwind base', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - img { - max-width: 100%; - } - button { - font-family: inherit; - } - ${defaults} - `) - }) - }) - - test('plugins can add base styles with raw PostCSS nodes', () => { - let config = { - content: [ - { - raw: html``, - }, - ], - plugins: [ - function ({ addBase, postcss }) { - addBase([ - postcss.rule({ selector: 'img' }).append([ - postcss.decl({ - prop: 'max-width', - value: '100%', - }), - ]), - postcss.rule({ selector: 'button' }).append([ - postcss.decl({ - prop: 'font-family', - value: 'inherit', - }), - ]), - ]) - }, - ], - corePlugins: { preflight: false }, - } - - return run('@tailwind base', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - img { - max-width: 100%; - } - button { - font-family: inherit; - } - ${defaults} - `) - }) - }) - - test('plugins can create components with raw PostCSS nodes', () => { - let config = { - content: [ - { - raw: html``, - }, - ], - plugins: [ - function ({ addComponents, postcss }) { - addComponents([ - postcss.rule({ selector: '.btn-blue' }).append([ - postcss.decl({ - prop: 'background-color', - value: 'blue', - }), - postcss.decl({ - prop: 'color', - value: 'white', - }), - postcss.decl({ - prop: 'padding', - value: '.5rem 1rem', - }), - postcss.decl({ - prop: 'border-radius', - value: '.25rem', - }), - ]), - postcss.rule({ selector: '.btn-blue:hover' }).append([ - postcss.decl({ - prop: 'background-color', - value: 'darkblue', - }), - ]), - ]) - }, - ], - } - - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .btn-blue { - color: #fff; - background-color: #00f; - border-radius: 0.25rem; - padding: 0.5rem 1rem; - } - .btn-blue:hover { - background-color: #00008b; - } - `) - }) - }) - - test('plugins can create components with mixed object styles and raw PostCSS nodes', () => { - let config = { - content: [ - { - raw: html``, - }, - ], - plugins: [ - function ({ addComponents, postcss }) { - addComponents([ - postcss.rule({ selector: '.btn-blue' }).append([ - postcss.decl({ - prop: 'background-color', - value: 'blue', - }), - postcss.decl({ - prop: 'color', - value: 'white', - }), - postcss.decl({ - prop: 'padding', - value: '.5rem 1rem', - }), - postcss.decl({ - prop: 'border-radius', - value: '.25rem', - }), - ]), - { - '.btn-blue:hover': { - backgroundColor: 'darkblue', - }, + }, + '@media (min-width: 300px)': { + '.custom-container': { + maxWidth: '300px', }, - ]) - }, - ], - } + }, + }) + }, + ], + } - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .btn-blue { - color: #fff; - background-color: #00f; - border-radius: 0.25rem; - padding: 0.5rem 1rem; + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .custom-container { + width: 100%; + } + @media (min-width: 100px) { + .custom-container { + max-width: 100px; } - .btn-blue:hover { - background-color: #00008b; + } + @media (min-width: 200px) { + .custom-container { + max-width: 200px; } - `) - }) + } + @media (min-width: 300px) { + .custom-container { + max-width: 300px; + } + } + `) }) +}) - test('plugins can create components with media queries with object syntax', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - plugins: [ - function ({ addComponents }) { - addComponents({ +test('media queries can be defined multiple times using objects-in-array syntax', () => { + let config = { + content: [ + { + raw: html`
+ `, + }, + ], + plugins: [ + function ({ addComponents }) { + addComponents([ + { '.custom-container': { width: '100%', }, @@ -425,993 +475,759 @@ crosscheck(() => { maxWidth: '100px', }, }, - '@media (min-width: 200px)': { - '.custom-container': { - maxWidth: '200px', - }, + }, + { + '.btn': { + padding: '1rem .5rem', + display: 'block', }, - '@media (min-width: 300px)': { - '.custom-container': { - maxWidth: '300px', + '@media (min-width: 100px)': { + '.btn': { + display: 'inline-block', }, }, - }) - }, - ], - } + }, + ]) + }, + ], + } - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .custom-container { + width: 100%; + } + @media (min-width: 100px) { .custom-container { - width: 100%; - } - @media (min-width: 100px) { - .custom-container { - max-width: 100px; - } - } - @media (min-width: 200px) { - .custom-container { - max-width: 200px; - } + max-width: 100px; } - @media (min-width: 300px) { - .custom-container { - max-width: 300px; - } + } + .btn { + padding: 1rem 0.5rem; + display: block; + } + @media (min-width: 100px) { + .btn { + display: inline-block; } - `) - }) + } + `) }) +}) - test('media queries can be defined multiple times using objects-in-array syntax', () => { - let config = { - content: [ - { - raw: html`
- `, - }, - ], - plugins: [ - function ({ addComponents }) { - addComponents([ - { - '.custom-container': { - width: '100%', - }, - '@media (min-width: 100px)': { - '.custom-container': { - maxWidth: '100px', - }, - }, +test('plugins can create nested rules', () => { + let config = { + content: [{ raw: html`` }], + plugins: [ + function ({ addComponents }) { + addComponents({ + '.btn-blue': { + backgroundColor: 'blue', + color: 'white', + padding: '.5rem 1rem', + borderRadius: '.25rem', + '&:hover': { + backgroundColor: 'darkblue', }, - { - '.btn': { - padding: '1rem .5rem', - display: 'block', - }, - '@media (min-width: 100px)': { - '.btn': { - display: 'inline-block', - }, + '@media (min-width: 500px)': { + '&:hover': { + backgroundColor: 'orange', }, }, - ]) - }, - ], - } - - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .custom-container { - width: 100%; - } - @media (min-width: 100px) { - .custom-container { - max-width: 100px; - } - } - .btn { - padding: 1rem 0.5rem; - display: block; - } - @media (min-width: 100px) { - .btn { - display: inline-block; - } + '> a': { + color: 'red', + }, + 'h1 &': { + color: 'purple', + }, + }, + }) + }, + ], + } + + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .btn-blue { + color: #fff; + background-color: #00f; + border-radius: 0.25rem; + padding: 0.5rem 1rem; + } + .btn-blue:hover { + background-color: #00008b; + } + @media (min-width: 500px) { + .btn-blue:hover { + background-color: orange; } - `) - }) + } + .btn-blue > a { + color: red; + } + h1 .btn-blue { + color: purple; + } + `) }) +}) - test('plugins can create nested rules', () => { - let config = { - content: [{ raw: html`` }], - plugins: [ - function ({ addComponents }) { - addComponents({ - '.btn-blue': { - backgroundColor: 'blue', - color: 'white', - padding: '.5rem 1rem', - borderRadius: '.25rem', - '&:hover': { - backgroundColor: 'darkblue', - }, - '@media (min-width: 500px)': { - '&:hover': { - backgroundColor: 'orange', - }, - }, - '> a': { - color: 'red', - }, - 'h1 &': { - color: 'purple', - }, - }, - }) - }, - ], - } +test('plugins can create rules with escaped selectors', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [ + function ({ e, addUtilities }) { + addUtilities({ + [`.${e('custom-top-1/4')}`]: { + top: '25%', + }, + }) + }, + ], + } - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .btn-blue { - color: #fff; - background-color: #00f; - border-radius: 0.25rem; - padding: 0.5rem 1rem; - } - .btn-blue:hover { - background-color: #00008b; - } - @media (min-width: 500px) { - .btn-blue:hover { - background-color: orange; - } - } - .btn-blue > a { - color: red; - } - h1 .btn-blue { - color: purple; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .custom-top-1\/4 { + top: 25%; + } + `) }) +}) - test('plugins can create rules with escaped selectors', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [ - function ({ e, addUtilities }) { - addUtilities({ - [`.${e('custom-top-1/4')}`]: { - top: '25%', +test('plugins can access the current config', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [ + function ({ addComponents, config }) { + let containerClasses = [ + { + '.custom-container': { + width: '100%', }, - }) - }, - ], - } - - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .custom-top-1\/4 { - top: 25%; - } - `) - }) - }) + }, + ] - test('plugins can access the current config', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [ - function ({ addComponents, config }) { - let containerClasses = [ - { - '.custom-container': { - width: '100%', - }, + for (let maxWidth of Object.values(config('theme.customScreens'))) { + containerClasses.push({ + [`@media (min-width: ${maxWidth})`]: { + '.custom-container': { maxWidth }, }, - ] - - for (let maxWidth of Object.values(config('theme.customScreens'))) { - containerClasses.push({ - [`@media (min-width: ${maxWidth})`]: { - '.custom-container': { maxWidth }, - }, - }) - } + }) + } - addComponents(containerClasses) - }, - ], - theme: { - customScreens: { - sm: '576px', - md: '768px', - lg: '992px', - xl: '1200px', - }, + addComponents(containerClasses) }, - } + ], + theme: { + customScreens: { + sm: '576px', + md: '768px', + lg: '992px', + xl: '1200px', + }, + }, + } - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .custom-container { + width: 100%; + } + @media (min-width: 576px) { .custom-container { - width: 100%; + max-width: 576px; } - @media (min-width: 576px) { - .custom-container { - max-width: 576px; - } - } - @media (min-width: 768px) { - .custom-container { - max-width: 768px; - } + } + @media (min-width: 768px) { + .custom-container { + max-width: 768px; } - @media (min-width: 992px) { - .custom-container { - max-width: 992px; - } + } + @media (min-width: 992px) { + .custom-container { + max-width: 992px; } - @media (min-width: 1200px) { - .custom-container { - max-width: 1200px; - } + } + @media (min-width: 1200px) { + .custom-container { + max-width: 1200px; } - `) - }) + } + `) }) +}) - test('plugins can check if corePlugins are enabled', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [ - function ({ addUtilities, corePlugins }) { - addUtilities({ - '.test': { - 'text-color': corePlugins('textColor') ? 'true' : 'false', - opacity: corePlugins('opacity') ? 'true' : 'false', - }, - }) - }, - ], - corePlugins: { textColor: false }, - } - - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .test { - text-color: false; - opacity: true; - } - `) - }) +test('plugins can check if corePlugins are enabled', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [ + function ({ addUtilities, corePlugins }) { + addUtilities({ + '.test': { + 'text-color': corePlugins('textColor') ? 'true' : 'false', + opacity: corePlugins('opacity') ? 'true' : 'false', + }, + }) + }, + ], + corePlugins: { textColor: false }, + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .test { + text-color: false; + opacity: true; + } + `) }) +}) - test('plugins can check if corePlugins are enabled when using array white-listing', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [ - function ({ addUtilities, corePlugins }) { - addUtilities({ - '.test': { - 'text-color': corePlugins('textColor') ? 'true' : 'false', - opacity: corePlugins('opacity') ? 'true' : 'false', - }, - }) - }, - ], - corePlugins: ['textColor'], - } - - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .test { - text-color: true; - opacity: false; - } - `) - }) +test('plugins can check if corePlugins are enabled when using array white-listing', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [ + function ({ addUtilities, corePlugins }) { + addUtilities({ + '.test': { + 'text-color': corePlugins('textColor') ? 'true' : 'false', + opacity: corePlugins('opacity') ? 'true' : 'false', + }, + }) + }, + ], + corePlugins: ['textColor'], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .test { + text-color: true; + opacity: false; + } + `) }) +}) - test('plugins can provide fallbacks to keys missing from the config', () => { - let config = { - content: [{ raw: html`` }], - plugins: [ - function ({ addComponents, config }) { - addComponents({ - '.btn': { - borderRadius: config('borderRadius.default', '.25rem'), - }, - }) - }, - ], - theme: { - borderRadius: { - 1: '1px', - 2: '2px', - 4: '4px', - 8: '8px', - }, +test('plugins can provide fallbacks to keys missing from the config', () => { + let config = { + content: [{ raw: html`` }], + plugins: [ + function ({ addComponents, config }) { + addComponents({ + '.btn': { + borderRadius: config('borderRadius.default', '.25rem'), + }, + }) }, - } + ], + theme: { + borderRadius: { + 1: '1px', + 2: '2px', + 4: '4px', + 8: '8px', + }, + }, + } - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .btn { - border-radius: 0.25rem; - } - `) - }) + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .btn { + border-radius: 0.25rem; + } + `) }) +}) - test('plugins can add multiple sets of utilities and components', () => { - let config = { - content: [ - { - raw: html` -
`, - }, - ], - plugins: [ - function ({ addUtilities, addComponents }) { - addComponents({ - '.card': { - padding: '1rem', - borderRadius: '.25rem', - }, - }) - - addUtilities({ - '.custom-skew-12deg': { - transform: 'skewY(-12deg)', - }, - }) - - addComponents({ - '.btn': { - padding: '1rem .5rem', - display: 'inline-block', - }, - }) - - addUtilities({ - '.custom-border-collapse': { - borderCollapse: 'collapse', - }, - }) - }, - ], - } +test('plugins can add multiple sets of utilities and components', () => { + let config = { + content: [ + { + raw: html` +
`, + }, + ], + plugins: [ + function ({ addUtilities, addComponents }) { + addComponents({ + '.card': { + padding: '1rem', + borderRadius: '.25rem', + }, + }) - return run('@tailwind components; @tailwind utilities;', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .card { - border-radius: 0.25rem; - padding: 1rem; - } - .btn { - padding: 1rem 0.5rem; - display: inline-block; - } - .custom-skew-12deg { - transform: skewY(-12deg); - } - .custom-border-collapse { - border-collapse: collapse; - } - `) - }) - }) + addUtilities({ + '.custom-skew-12deg': { + transform: 'skewY(-12deg)', + }, + }) - test('plugins respect prefix and important options by default when adding utilities', () => { - let config = { - prefix: 'tw-', - important: true, - content: [{ raw: html`
` }], - plugins: [ - function ({ addUtilities }) { - addUtilities({ - '.custom-rotate-90': { - transform: 'rotate(90deg)', - }, - }) - }, - ], - } + addComponents({ + '.btn': { + padding: '1rem .5rem', + display: 'inline-block', + }, + }) - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .tw-custom-rotate-90 { - transform: rotate(90deg) !important; - } - `) - }) + addUtilities({ + '.custom-border-collapse': { + borderCollapse: 'collapse', + }, + }) + }, + ], + } + + return run('@tailwind components; @tailwind utilities;', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .card { + border-radius: 0.25rem; + padding: 1rem; + } + .btn { + padding: 1rem 0.5rem; + display: inline-block; + } + .custom-skew-12deg { + transform: skewY(-12deg); + } + .custom-border-collapse { + border-collapse: collapse; + } + `) }) +}) - test('when important is a selector it is used to scope utilities instead of adding !important', () => { - let config = { - prefix: 'tw-', - important: '#app', - content: [{ raw: html`
` }], - plugins: [ - function ({ addUtilities }) { - addUtilities({ - '.custom-rotate-90': { - transform: 'rotate(90deg)', - }, - }) - }, - ], - } +test('plugins respect prefix and important options by default when adding utilities', () => { + let config = { + prefix: 'tw-', + important: true, + content: [{ raw: html`
` }], + plugins: [ + function ({ addUtilities }) { + addUtilities({ + '.custom-rotate-90': { + transform: 'rotate(90deg)', + }, + }) + }, + ], + } - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - #app .tw-custom-rotate-90 { - transform: rotate(90deg); - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .tw-custom-rotate-90 { + transform: rotate(90deg) !important; + } + `) }) +}) - test('when important is a selector it scopes all selectors in a rule, even though defining utilities like this is stupid', () => { - let config = { - important: '#app', - content: [{ raw: html`
` }], - plugins: [ - function ({ addUtilities }) { - addUtilities({ - '.custom-rotate-90, .custom-rotate-1\\/4': { - transform: 'rotate(90deg)', - }, - }) - }, - ], - } +test('when important is a selector it is used to scope utilities instead of adding !important', () => { + let config = { + prefix: 'tw-', + important: '#app', + content: [{ raw: html`
` }], + plugins: [ + function ({ addUtilities }) { + addUtilities({ + '.custom-rotate-90': { + transform: 'rotate(90deg)', + }, + }) + }, + ], + } - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - #app .custom-rotate-90, - #app .custom-rotate-1\/4 { - transform: rotate(90deg); - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + #app .tw-custom-rotate-90 { + transform: rotate(90deg); + } + `) }) +}) - test('important utilities are not made double important when important option is used', () => { - let config = { - important: true, - content: [{ raw: html`
` }], - plugins: [ - function ({ addUtilities }) { - addUtilities({ - '.custom-rotate-90': { - transform: 'rotate(90deg) !important', - }, - }) - }, - ], - } - - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .custom-rotate-90 { - transform: rotate(90deg) !important; - } - `) - }) +test('when important is a selector it scopes all selectors in a rule, even though defining utilities like this is stupid', () => { + let config = { + important: '#app', + content: [{ raw: html`
` }], + plugins: [ + function ({ addUtilities }) { + addUtilities({ + '.custom-rotate-90, .custom-rotate-1\\/4': { + transform: 'rotate(90deg)', + }, + }) + }, + ], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + #app .custom-rotate-90, + #app .custom-rotate-1\/4 { + transform: rotate(90deg); + } + `) }) +}) - test("component declarations respect the 'prefix' option by default", () => { - let config = { - prefix: 'tw-', - content: [{ raw: html`` }], - plugins: [ - function ({ addComponents }) { - addComponents({ - '.btn-blue': { - backgroundColor: 'blue', - }, - }) - }, - ], - } +test('important utilities are not made double important when important option is used', () => { + let config = { + important: true, + content: [{ raw: html`
` }], + plugins: [ + function ({ addUtilities }) { + addUtilities({ + '.custom-rotate-90': { + transform: 'rotate(90deg) !important', + }, + }) + }, + ], + } - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .tw-btn-blue { - background-color: #00f; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .custom-rotate-90 { + transform: rotate(90deg) !important; + } + `) }) +}) - test('all selectors in a rule are prefixed', () => { - let config = { - prefix: 'tw-', - content: [ - { - raw: html` -
`, - }, - ], - plugins: [ - function ({ addUtilities, addComponents }) { - addUtilities({ - '.custom-rotate-90, .custom-rotate-1\\/4': { - transform: 'rotate(90deg)', - }, - }) - addComponents({ - '.btn-blue, .btn-red': { - padding: '10px', - }, - }) - }, - ], - } +test("component declarations respect the 'prefix' option by default", () => { + let config = { + prefix: 'tw-', + content: [{ raw: html`` }], + plugins: [ + function ({ addComponents }) { + addComponents({ + '.btn-blue': { + backgroundColor: 'blue', + }, + }) + }, + ], + } - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .tw-btn-blue, - .tw-btn-red { - padding: 10px; - } - `) - }) + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .tw-btn-blue { + background-color: #00f; + } + `) }) +}) - test("component declarations can optionally ignore 'prefix' option", () => { - let config = { - prefix: 'tw-', - content: [{ raw: html`` }], - plugins: [ - function ({ addComponents }) { - addComponents( - { - '.btn-blue': { - backgroundColor: 'blue', - }, - }, - { respectPrefix: false } - ) - }, - ], - } - - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .btn-blue { - background-color: #00f; - } - `) - }) +test('all selectors in a rule are prefixed', () => { + let config = { + prefix: 'tw-', + content: [ + { + raw: html` +
`, + }, + ], + plugins: [ + function ({ addUtilities, addComponents }) { + addUtilities({ + '.custom-rotate-90, .custom-rotate-1\\/4': { + transform: 'rotate(90deg)', + }, + }) + addComponents({ + '.btn-blue, .btn-red': { + padding: '10px', + }, + }) + }, + ], + } + + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .tw-btn-blue, + .tw-btn-red { + padding: 10px; + } + `) }) +}) - test("component declarations are not affected by the 'important' option", () => { - let config = { - important: true, - content: [{ raw: html`` }], - plugins: [ - function ({ addComponents }) { - addComponents({ +test("component declarations can optionally ignore 'prefix' option", () => { + let config = { + prefix: 'tw-', + content: [{ raw: html`` }], + plugins: [ + function ({ addComponents }) { + addComponents( + { '.btn-blue': { backgroundColor: 'blue', }, - }) - }, - ], - } - - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .btn-blue { - background-color: #00f; - } - `) - }) - }) - - test("plugins can apply the user's chosen prefix to components manually", () => { - let config = { - prefix: 'tw-', - content: [{ raw: html`` }], - plugins: [ - function ({ addComponents, prefix }) { - addComponents( - { - [prefix('.btn-blue')]: { - backgroundColor: 'blue', - }, - }, - { respectPrefix: false } - ) - }, - ], - } + }, + { respectPrefix: false } + ) + }, + ], + } - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .tw-btn-blue { - background-color: #00f; - } - `) - }) + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .btn-blue { + background-color: #00f; + } + `) }) +}) - test('prefix can optionally be ignored for utilities', () => { - let config = { - prefix: 'tw-', - content: [{ raw: html`
` }], - plugins: [ - function ({ addUtilities }) { - addUtilities( - { - '.custom-rotate-90': { - transform: 'rotate(90deg)', - }, - }, - { respectPrefix: false } - ) - }, - ], - } +test("component declarations are not affected by the 'important' option", () => { + let config = { + important: true, + content: [{ raw: html`` }], + plugins: [ + function ({ addComponents }) { + addComponents({ + '.btn-blue': { + backgroundColor: 'blue', + }, + }) + }, + ], + } - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .custom-rotate-90 { - transform: rotate(90deg); - } - `) - }) + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .btn-blue { + background-color: #00f; + } + `) }) +}) - test('important can optionally be ignored for utilities', () => { - let config = { - important: true, - content: [{ raw: html`
` }], - plugins: [ - function ({ addUtilities }) { - addUtilities( - { - '.custom-rotate-90': { - transform: 'rotate(90deg)', - }, +test("plugins can apply the user's chosen prefix to components manually", () => { + let config = { + prefix: 'tw-', + content: [{ raw: html`` }], + plugins: [ + function ({ addComponents, prefix }) { + addComponents( + { + [prefix('.btn-blue')]: { + backgroundColor: 'blue', }, - { respectImportant: false } - ) - }, - ], - } + }, + { respectPrefix: false } + ) + }, + ], + } - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .custom-rotate-90 { - transform: rotate(90deg); - } - `) - }) + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .tw-btn-blue { + background-color: #00f; + } + `) }) +}) - test('prefix will prefix all classes in a selector', () => { - let config = { - prefix: 'tw-', - content: [{ raw: html`
` }], - plugins: [ - function ({ addComponents, prefix }) { - addComponents( - { - [prefix('.btn-blue .w-1\\/4 > h1.text-xl + a .bar')]: { - backgroundColor: 'blue', - }, +test('prefix can optionally be ignored for utilities', () => { + let config = { + prefix: 'tw-', + content: [{ raw: html`
` }], + plugins: [ + function ({ addUtilities }) { + addUtilities( + { + '.custom-rotate-90': { + transform: 'rotate(90deg)', }, - { respectPrefix: false } - ) - }, - ], - } - - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .tw-btn-blue .tw-w-1\/4 > h1.tw-text-xl + a .tw-bar { - background-color: #00f; - } - `) - }) - }) - - test('plugins can be provided as an object with a handler function', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - plugins: [ - { - handler({ addUtilities }) { - addUtilities({ - '.custom-object-fill': { - 'object-fit': 'fill', - }, - '.custom-object-contain': { - 'object-fit': 'contain', - }, - '.custom-object-cover': { - 'object-fit': 'cover', - }, - }) }, - }, - ], - } + { respectPrefix: false } + ) + }, + ], + } - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .custom-object-fill { - object-fit: fill; - } - .custom-object-contain { - object-fit: contain; - } - .custom-object-cover { - object-fit: cover; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .custom-rotate-90 { + transform: rotate(90deg); + } + `) }) +}) - test('plugins can provide a config but no handler', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - plugins: [ - { - config: { - prefix: 'tw-', - }, - }, - { - handler({ addUtilities }) { - addUtilities({ - '.custom-object-fill': { - 'object-fit': 'fill', - }, - '.custom-object-contain': { - 'object-fit': 'contain', - }, - '.custom-object-cover': { - 'object-fit': 'cover', - }, - }) +test('important can optionally be ignored for utilities', () => { + let config = { + important: true, + content: [{ raw: html`
` }], + plugins: [ + function ({ addUtilities }) { + addUtilities( + { + '.custom-rotate-90': { + transform: 'rotate(90deg)', + }, }, - }, - ], - } + { respectImportant: false } + ) + }, + ], + } - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .tw-custom-object-fill { - object-fit: fill; - } - .tw-custom-object-contain { - object-fit: contain; - } - .tw-custom-object-cover { - object-fit: cover; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .custom-rotate-90 { + transform: rotate(90deg); + } + `) }) +}) - test('plugins can be created using the `createPlugin` function', () => { - let config = { - content: [ - { raw: html`
` }, - ], - corePlugins: [], - theme: { - screens: { - sm: '400px', - }, - }, - plugins: [ - createPlugin( - function ({ addUtilities, theme }) { - addUtilities( - Object.fromEntries( - Object.entries(theme('testPlugin')).map(([k, v]) => [ - `.test-${k}`, - { testProperty: v }, - ]) - ) - ) - }, +test('prefix will prefix all classes in a selector', () => { + let config = { + prefix: 'tw-', + content: [{ raw: html`
` }], + plugins: [ + function ({ addComponents, prefix }) { + addComponents( { - theme: { - testPlugin: { - sm: '1rem', - md: '2rem', - lg: '3rem', - }, + [prefix('.btn-blue .w-1\\/4 > h1.text-xl + a .bar')]: { + backgroundColor: 'blue', }, - } - ), - ], - } + }, + { respectPrefix: false } + ) + }, + ], + } - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .test-sm { - test-property: 1rem; - } - .test-md { - test-property: 2rem; - } - .test-lg { - test-property: 3rem; - } - .hover\:test-sm:hover { - test-property: 1rem; - } - @media (min-width: 400px) { - .sm\:test-sm { - test-property: 1rem; - } - } - `) - }) + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .tw-btn-blue .tw-w-1\/4 > h1.tw-text-xl + a .tw-bar { + background-color: #00f; + } + `) }) +}) - test('plugins with extra options can be created using the `createPlugin.withOptions` function', () => { - let plugin = createPlugin.withOptions( - function ({ className }) { - return function ({ addUtilities, theme }) { - addUtilities( - Object.fromEntries( - Object.entries(theme('testPlugin')).map(([k, v]) => [ - `.${className}-${k}`, - { testProperty: v }, - ]) - ) - ) - } +test('plugins can be provided as an object with a handler function', () => { + let config = { + content: [ + { + raw: html`
`, }, - function () { - return { - theme: { - testPlugin: { - sm: '1rem', - md: '2rem', - lg: '3rem', + ], + plugins: [ + { + handler({ addUtilities }) { + addUtilities({ + '.custom-object-fill': { + 'object-fit': 'fill', }, - }, - } - } - ) - - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: [], - theme: { - screens: { - sm: '400px', + '.custom-object-contain': { + 'object-fit': 'contain', + }, + '.custom-object-cover': { + 'object-fit': 'cover', + }, + }) }, }, - plugins: [plugin({ className: 'banana' })], - } + ], + } - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .banana-sm { - test-property: 1rem; - } - .banana-md { - test-property: 2rem; - } - .banana-lg { - test-property: 3rem; - } - .hover\:banana-sm:hover { - test-property: 1rem; - } - @media (min-width: 400px) { - .sm\:banana-sm { - test-property: 1rem; - } - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .custom-object-fill { + object-fit: fill; + } + .custom-object-contain { + object-fit: contain; + } + .custom-object-cover { + object-fit: cover; + } + `) }) +}) - test('plugins should cache correctly', () => { - let plugin = createPlugin.withOptions( - ({ className = 'banana' } = {}) => - ({ addComponents }) => { - addComponents({ [`.${className}`]: { position: 'absolute' } }) - } - ) - - let config = { - content: [{ raw: html`
` }], - corePlugins: [], - theme: { - screens: { - sm: '400px', +test('plugins can provide a config but no handler', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + plugins: [ + { + config: { + prefix: 'tw-', }, }, - } - - function internalRun(options = {}) { - return run('@tailwind components', { - ...config, - plugins: [plugin(options)], - }) - } - - return Promise.all([internalRun(), internalRun({ className: 'apple' })]).then( - ([result1, result2]) => { - let expected1 = css` - .banana { - position: absolute; - } - @media (min-width: 400px) { - .sm\:banana { - position: absolute; - } - } - ` - - let expected2 = css` - .apple { - position: absolute; - } - @media (min-width: 400px) { - .sm\:apple { - position: absolute; - } - } - ` + { + handler({ addUtilities }) { + addUtilities({ + '.custom-object-fill': { + 'object-fit': 'fill', + }, + '.custom-object-contain': { + 'object-fit': 'contain', + }, + '.custom-object-cover': { + 'object-fit': 'cover', + }, + }) + }, + }, + ], + } - expect(result1.css).toMatchCss(expected1) - expect(result2.css).toMatchCss(expected2) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .tw-custom-object-fill { + object-fit: fill; } - ) + .tw-custom-object-contain { + object-fit: contain; + } + .tw-custom-object-cover { + object-fit: cover; + } + `) }) +}) - test('plugins created using `createPlugin.withOptions` do not need to be invoked if the user wants to use the default options', () => { - let plugin = createPlugin.withOptions( - function ({ className } = { className: 'banana' }) { - return function ({ addUtilities, theme }) { +test('plugins can be created using the `createPlugin` function', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: [], + theme: { + screens: { + sm: '400px', + }, + }, + plugins: [ + createPlugin( + function ({ addUtilities, theme }) { addUtilities( Object.fromEntries( Object.entries(theme('testPlugin')).map(([k, v]) => [ - `.${className}-${k}`, + `.test-${k}`, { testProperty: v }, ]) ) ) - } - }, - function () { - return { + }, + { theme: { testPlugin: { sm: '1rem', @@ -1420,49 +1236,153 @@ crosscheck(() => { }, }, } + ), + ], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .test-sm { + test-property: 1rem; + } + .test-md { + test-property: 2rem; + } + .test-lg { + test-property: 3rem; + } + .hover\:test-sm:hover { + test-property: 1rem; } - ) + @media (min-width: 400px) { + .sm\:test-sm { + test-property: 1rem; + } + } + `) + }) +}) - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: [], - theme: { - screens: { - sm: '400px', +test('plugins with extra options can be created using the `createPlugin.withOptions` function', () => { + let plugin = createPlugin.withOptions( + function ({ className }) { + return function ({ addUtilities, theme }) { + addUtilities( + Object.fromEntries( + Object.entries(theme('testPlugin')).map(([k, v]) => [ + `.${className}-${k}`, + { testProperty: v }, + ]) + ) + ) + } + }, + function () { + return { + theme: { + testPlugin: { + sm: '1rem', + md: '2rem', + lg: '3rem', + }, }, - }, - plugins: [plugin], + } } + ) - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .banana-sm { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: [], + theme: { + screens: { + sm: '400px', + }, + }, + plugins: [plugin({ className: 'banana' })], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .banana-sm { + test-property: 1rem; + } + .banana-md { + test-property: 2rem; + } + .banana-lg { + test-property: 3rem; + } + .hover\:banana-sm:hover { + test-property: 1rem; + } + @media (min-width: 400px) { + .sm\:banana-sm { test-property: 1rem; } - .banana-md { - test-property: 2rem; + } + `) + }) +}) + +test('plugins should cache correctly', () => { + let plugin = createPlugin.withOptions(({ className = 'banana' } = {}) => ({ addComponents }) => { + addComponents({ [`.${className}`]: { position: 'absolute' } }) + }) + + let config = { + content: [{ raw: html`
` }], + corePlugins: [], + theme: { + screens: { + sm: '400px', + }, + }, + } + + function internalRun(options = {}) { + return run('@tailwind components', { + ...config, + plugins: [plugin(options)], + }) + } + + return Promise.all([internalRun(), internalRun({ className: 'apple' })]).then( + ([result1, result2]) => { + let expected1 = css` + .banana { + position: absolute; } - .banana-lg { - test-property: 3rem; + @media (min-width: 400px) { + .sm\:banana { + position: absolute; + } } - .hover\:banana-sm:hover { - test-property: 1rem; + ` + + let expected2 = css` + .apple { + position: absolute; } @media (min-width: 400px) { - .sm\:banana-sm { - test-property: 1rem; + .sm\:apple { + position: absolute; } } - `) - }) - }) + ` - test('the configFunction parameter is optional when using the `createPlugin.withOptions` function', () => { - let plugin = createPlugin.withOptions(function ({ className }) { + expect(result1.css).toMatchCss(expected1) + expect(result2.css).toMatchCss(expected2) + } + ) +}) + +test('plugins created using `createPlugin.withOptions` do not need to be invoked if the user wants to use the default options', () => { + let plugin = createPlugin.withOptions( + function ({ className } = { className: 'banana' }) { return function ({ addUtilities, theme }) { addUtilities( Object.fromEntries( @@ -1473,461 +1393,524 @@ crosscheck(() => { ) ) } - }) - - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: [], - theme: { - screens: { - sm: '400px', - }, - testPlugin: { - sm: '1px', - md: '2px', - lg: '3px', + }, + function () { + return { + theme: { + testPlugin: { + sm: '1rem', + md: '2rem', + lg: '3rem', + }, }, - }, - plugins: [plugin({ className: 'banana' })], + } } + ) - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .banana-sm { - test-property: 1px; - } - .banana-md { - test-property: 2px; - } - .banana-lg { - test-property: 3px; + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: [], + theme: { + screens: { + sm: '400px', + }, + }, + plugins: [plugin], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .banana-sm { + test-property: 1rem; + } + .banana-md { + test-property: 2rem; + } + .banana-lg { + test-property: 3rem; + } + .hover\:banana-sm:hover { + test-property: 1rem; + } + @media (min-width: 400px) { + .sm\:banana-sm { + test-property: 1rem; } - .hover\:banana-sm:hover { + } + `) + }) +}) + +test('the configFunction parameter is optional when using the `createPlugin.withOptions` function', () => { + let plugin = createPlugin.withOptions(function ({ className }) { + return function ({ addUtilities, theme }) { + addUtilities( + Object.fromEntries( + Object.entries(theme('testPlugin')).map(([k, v]) => [ + `.${className}-${k}`, + { testProperty: v }, + ]) + ) + ) + } + }) + + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: [], + theme: { + screens: { + sm: '400px', + }, + testPlugin: { + sm: '1px', + md: '2px', + lg: '3px', + }, + }, + plugins: [plugin({ className: 'banana' })], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .banana-sm { + test-property: 1px; + } + .banana-md { + test-property: 2px; + } + .banana-lg { + test-property: 3px; + } + .hover\:banana-sm:hover { + test-property: 1px; + } + @media (min-width: 400px) { + .sm\:banana-sm { test-property: 1px; } - @media (min-width: 400px) { - .sm\:banana-sm { - test-property: 1px; - } - } - `) - }) + } + `) }) +}) - test('keyframes are not escaped', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - plugins: [ - function ({ matchUtilities }) { - matchUtilities({ - foo: (value) => { - return { - [`@keyframes ${value}`]: { - '25.001%': { - color: 'black', - }, +test('keyframes are not escaped', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + plugins: [ + function ({ matchUtilities }) { + matchUtilities({ + foo: (value) => { + return { + [`@keyframes ${value}`]: { + '25.001%': { + color: 'black', }, - animation: `${value} 1s infinite`, - } - }, - }) - }, - ], - } + }, + animation: `${value} 1s infinite`, + } + }, + }) + }, + ], + } - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - @keyframes abc { + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + @keyframes abc { + 25.001% { + color: #000; + } + } + .foo-\[abc\] { + animation: 1s infinite abc; + } + @media (min-width: 768px) { + @keyframes def { 25.001% { color: #000; } } - .foo-\[abc\] { - animation: 1s infinite abc; - } - @media (min-width: 768px) { - @keyframes def { - 25.001% { - color: #000; - } - } - .md\:foo-\[def\] { - animation: 1s infinite def; - } + .md\:foo-\[def\] { + animation: 1s infinite def; } - `) - }) + } + `) }) +}) - test('font sizes are retrieved without default line-heights or letter-spacing using theme function', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: [], - theme: { - fontSize: { - sm: ['14px', '20px'], - }, +test('font sizes are retrieved without default line-heights or letter-spacing using theme function', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: [], + theme: { + fontSize: { + sm: ['14px', '20px'], }, - plugins: [ - function ({ addComponents, theme }) { - addComponents({ - '.foo': { - fontSize: theme('fontSize.sm'), - }, - }) - }, - ], - } + }, + plugins: [ + function ({ addComponents, theme }) { + addComponents({ + '.foo': { + fontSize: theme('fontSize.sm'), + }, + }) + }, + ], + } - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .foo { - font-size: 14px; - } - `) - }) + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo { + font-size: 14px; + } + `) }) +}) - test('outlines are retrieved without outline-offset using theme function', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: [], - theme: { - outline: { - black: ['2px dotted black', '4px'], - }, +test('outlines are retrieved without outline-offset using theme function', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: [], + theme: { + outline: { + black: ['2px dotted black', '4px'], }, - plugins: [ - function ({ addComponents, theme }) { - addComponents({ - '.foo': { - outline: theme('outline.black'), - }, - }) - }, - ], - } + }, + plugins: [ + function ({ addComponents, theme }) { + addComponents({ + '.foo': { + outline: theme('outline.black'), + }, + }) + }, + ], + } - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .foo { - outline: 2px dotted #000; - } - `) - }) + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo { + outline: 2px dotted #000; + } + `) }) +}) - test('box-shadow values are joined when retrieved using the theme function', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: [], - theme: { - boxShadow: { - lol: ['width', 'height'], - }, +test('box-shadow values are joined when retrieved using the theme function', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: [], + theme: { + boxShadow: { + lol: ['width', 'height'], }, - plugins: [ - function ({ addComponents, theme }) { - addComponents({ - '.foo': { - boxShadow: theme('boxShadow.lol'), - }, - }) - }, - ], - } + }, + plugins: [ + function ({ addComponents, theme }) { + addComponents({ + '.foo': { + boxShadow: theme('boxShadow.lol'), + }, + }) + }, + ], + } - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .foo { - box-shadow: width, height; - } - `) - }) + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo { + box-shadow: width, height; + } + `) }) +}) - test('transition-property values are joined when retrieved using the theme function', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: [], - theme: { - transitionProperty: { - lol: ['width', 'height'], - }, +test('transition-property values are joined when retrieved using the theme function', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: [], + theme: { + transitionProperty: { + lol: ['width', 'height'], }, - plugins: [ - function ({ addComponents, theme }) { - addComponents({ - '.foo': { - transitionProperty: theme('transitionProperty.lol'), - }, - }) - }, - ], - } + }, + plugins: [ + function ({ addComponents, theme }) { + addComponents({ + '.foo': { + transitionProperty: theme('transitionProperty.lol'), + }, + }) + }, + ], + } - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .foo { - transition-property: width, height; - } - `) - }) + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo { + transition-property: width, height; + } + `) }) +}) - test('transition-duration values are joined when retrieved using the theme function', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: [], - theme: { - transitionDuration: { - lol: ['width', 'height'], - }, +test('transition-duration values are joined when retrieved using the theme function', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: [], + theme: { + transitionDuration: { + lol: ['width', 'height'], }, - plugins: [ - function ({ addComponents, theme }) { - addComponents({ - '.foo': { - transitionDuration: theme('transitionDuration.lol'), - }, - }) - }, - ], - } + }, + plugins: [ + function ({ addComponents, theme }) { + addComponents({ + '.foo': { + transitionDuration: theme('transitionDuration.lol'), + }, + }) + }, + ], + } - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .foo { - transition-duration: width, height; - } - `) - }) + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo { + transition-duration: width, height; + } + `) }) +}) - test('transition-delay values are joined when retrieved using the theme function', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: [], - theme: { - transitionDuration: { - lol: ['width', 'height'], - }, +test('transition-delay values are joined when retrieved using the theme function', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: [], + theme: { + transitionDuration: { + lol: ['width', 'height'], }, - plugins: [ - function ({ addComponents, theme }) { - addComponents({ - '.foo': { - transitionDuration: theme('transitionDuration.lol'), - }, - }) - }, - ], - } + }, + plugins: [ + function ({ addComponents, theme }) { + addComponents({ + '.foo': { + transitionDuration: theme('transitionDuration.lol'), + }, + }) + }, + ], + } - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .foo { - transition-duration: width, height; - } - `) - }) + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo { + transition-duration: width, height; + } + `) }) +}) - test('transition-timing-function values are joined when retrieved using the theme function', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: [], - theme: { - transitionTimingFunction: { - lol: ['width', 'height'], - }, +test('transition-timing-function values are joined when retrieved using the theme function', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: [], + theme: { + transitionTimingFunction: { + lol: ['width', 'height'], }, - plugins: [ - function ({ addComponents, theme }) { - addComponents({ - '.foo': { - transitionTimingFunction: theme('transitionTimingFunction.lol'), - }, - }) - }, - ], - } + }, + plugins: [ + function ({ addComponents, theme }) { + addComponents({ + '.foo': { + transitionTimingFunction: theme('transitionTimingFunction.lol'), + }, + }) + }, + ], + } - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .foo { - transition-timing-function: width, height; - } - `) - }) + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo { + transition-timing-function: width, height; + } + `) }) +}) - test('background-image values are joined when retrieved using the theme function', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: [], - theme: { - backgroundImage: { - lol: ['width', 'height'], - }, +test('background-image values are joined when retrieved using the theme function', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: [], + theme: { + backgroundImage: { + lol: ['width', 'height'], }, - plugins: [ - function ({ addComponents, theme }) { - addComponents({ - '.foo': { - backgroundImage: theme('backgroundImage.lol'), - }, - }) - }, - ], - } + }, + plugins: [ + function ({ addComponents, theme }) { + addComponents({ + '.foo': { + backgroundImage: theme('backgroundImage.lol'), + }, + }) + }, + ], + } - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .foo { - background-image: width, height; - } - `) - }) + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo { + background-image: width, height; + } + `) }) +}) - test('background-size values are joined when retrieved using the theme function', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: [], - theme: { - backgroundSize: { - lol: ['width', 'height'], - }, +test('background-size values are joined when retrieved using the theme function', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: [], + theme: { + backgroundSize: { + lol: ['width', 'height'], }, - plugins: [ - function ({ addComponents, theme }) { - addComponents({ - '.foo': { - backgroundSize: theme('backgroundSize.lol'), - }, - }) - }, - ], - } + }, + plugins: [ + function ({ addComponents, theme }) { + addComponents({ + '.foo': { + backgroundSize: theme('backgroundSize.lol'), + }, + }) + }, + ], + } - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .foo { - background-size: width, height; - } - `) - }) + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo { + background-size: width, height; + } + `) }) +}) - test('background-color values are joined when retrieved using the theme function', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: [], - theme: { - backgroundColor: { - lol: ['width', 'height'], - }, +test('background-color values are joined when retrieved using the theme function', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: [], + theme: { + backgroundColor: { + lol: ['width', 'height'], }, - plugins: [ - function ({ addComponents, theme }) { - addComponents({ - '.foo': { - backgroundColor: theme('backgroundColor.lol'), - }, - }) - }, - ], - } + }, + plugins: [ + function ({ addComponents, theme }) { + addComponents({ + '.foo': { + backgroundColor: theme('backgroundColor.lol'), + }, + }) + }, + ], + } - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .foo { - background-color: width, height; - } - `) - }) + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo { + background-color: width, height; + } + `) }) +}) - test('cursor values are joined when retrieved using the theme function', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: [], - theme: { - cursor: { - lol: ['width', 'height'], - }, +test('cursor values are joined when retrieved using the theme function', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: [], + theme: { + cursor: { + lol: ['width', 'height'], }, - plugins: [ - function ({ addComponents, theme }) { - addComponents({ - '.foo': { - cursor: theme('cursor.lol'), - }, - }) - }, - ], - } + }, + plugins: [ + function ({ addComponents, theme }) { + addComponents({ + '.foo': { + cursor: theme('cursor.lol'), + }, + }) + }, + ], + } - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .foo { - cursor: width, height; - } - `) - }) + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo { + cursor: width, height; + } + `) }) +}) - test('animation values are joined when retrieved using the theme function', () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: [], - theme: { - animation: { - lol: ['width', 'height'], - }, +test('animation values are joined when retrieved using the theme function', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: [], + theme: { + animation: { + lol: ['width', 'height'], }, - plugins: [ - function ({ addComponents, theme }) { - addComponents({ - '.foo': { - animation: theme('animation.lol'), - }, - }) - }, - ], - } + }, + plugins: [ + function ({ addComponents, theme }) { + addComponents({ + '.foo': { + animation: theme('animation.lol'), + }, + }) + }, + ], + } - return run('@tailwind components', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .foo { - animation: width, height; - } - `) - }) + return run('@tailwind components', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo { + animation: width, height; + } + `) }) +}) - test('custom properties are not converted to kebab-case when added to base layer', () => { - let config = { - content: [], - plugins: [ - function ({ addBase }) { - addBase({ - ':root': { - '--colors-primaryThing-500': '0, 0, 255', - }, - }) - }, - ], - } +test('custom properties are not converted to kebab-case when added to base layer', () => { + let config = { + content: [], + plugins: [ + function ({ addBase }) { + addBase({ + ':root': { + '--colors-primaryThing-500': '0, 0, 255', + }, + }) + }, + ], + } - return run('@tailwind base', config).then((result) => { - expect(result.css).toContain(`--colors-primaryThing-500: 0, 0, 255;`) - }) + return run('@tailwind base', config).then((result) => { + expect(result.css).toContain(`--colors-primaryThing-500: 0, 0, 255;`) }) }) diff --git a/tests/custom-separator.test.js b/tests/custom-separator.test.js index aee13e67e2f3..61b41ddf167d 100644 --- a/tests/custom-separator.test.js +++ b/tests/custom-separator.test.js @@ -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` -
-
-
-
-
- `, - }, - ], - separator: '_', - } +test('custom separator', () => { + let config = { + darkMode: 'selector', + content: [ + { + raw: html` +
+
+
+
+
+ `, + }, + ], + 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; } - `) - }) + } + .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: '-', - } +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." - ) - }) + 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." + ) }) diff --git a/tests/custom-transformers.test.js b/tests/custom-transformers.test.js index 14e3abac08b0..2715a715532e 100644 --- a/tests/custom-transformers.test.js +++ b/tests/custom-transformers.test.js @@ -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`
` }], - transform: customTransformer, - }, - } +test('transform function', () => { + let config = { + content: { + files: [{ raw: html`
` }], + 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`
` }], - transform: { - DEFAULT: customTransformer, - }, +test('transform.DEFAULT', () => { + let config = { + content: { + files: [{ raw: html`
` }], + 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; + } + `) }) }) diff --git a/tests/customConfig.test.js b/tests/customConfig.test.js index 5b83f9f2e81f..b8f459144587 100644 --- a/tests/customConfig.test.js +++ b/tests/customConfig.test.js @@ -2,53 +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 = { + content: [{ raw: html`
` }], + 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 as an object', () => { - let config = { +test('custom config can be passed under the `config` property', () => { + let config = { + config: { content: [{ raw: html`
` }], 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; - } + 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`), - } +test('tailwind.config.cjs is picked up by default', () => { + return inTempDirectory(() => { + fs.writeFileSync( + path.resolve(cjsConfigFile), + javascript`module.exports = { + content: [{ raw: '
' }], + 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 +105,23 @@ crosscheck(() => { `) }) }) +}) - test('custom config can be passed under the `config` property', () => { - let config = { - config: { - content: [{ raw: html`
` }], +test('tailwind.config.js is picked up by default', () => { + return inTempDirectory(() => { + fs.writeFileSync( + path.resolve(defaultConfigFile), + javascript`module.exports = { + content: [{ raw: '
' }], 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 +131,13 @@ crosscheck(() => { `) }) }) +}) - test('tailwind.config.cjs is picked up by default', () => { - return inTempDirectory(() => { - fs.writeFileSync( - path.resolve(cjsConfigFile), - javascript`module.exports = { +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: '
' }], theme: { screens: { @@ -94,25 +145,25 @@ 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 = { +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: '
' }], theme: { screens: { @@ -120,271 +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.cjs is picked up by default when passing an empty object', () => { - return inTempDirectory(() => { - fs.writeFileSync( - path.resolve(cjsConfigFile), - javascript`module.exports = { - content: [{ raw: '
' }], +test('the default config can be overridden using the presets key', () => { + let config = { + content: [{ raw: html`
` }], + 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: '
' }], +test('presets can be functions', () => { + let config = { + content: [{ raw: html`
` }], + 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('the default config can be overridden using the presets key', () => { - let config = { - content: [{ raw: html`
` }], - presets: [ - { - theme: { - extend: { minHeight: { secondary: '24px' } }, - }, +test('the default config can be removed by using an empty presets key in a preset', () => { + let config = { + content: [{ raw: html`
` }], + presets: [ + { + presets: [], + theme: { + 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; - } - `) - }) - }) - - test('presets can be functions', () => { - let config = { - content: [{ raw: html`
` }], - 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; - } - `) - }) + 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('the default config can be removed by using an empty presets key in a preset', () => { - let config = { - content: [{ raw: html`
` }], - presets: [ - { - presets: [], - theme: { - extend: { minHeight: { secondary: '24px' } }, - }, +test('presets can have their own presets', () => { + let config = { + content: [{ raw: html`
` }], + 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`
` }], - presets: [ - { - presets: [], - theme: { - colors: { red: '#dd0000' }, - }, - }, - { - presets: [ - { - presets: [], - theme: { - colors: { - transparent: 'transparent', - red: '#ff0000', - }, - }, - }, - ], - theme: { - extend: { + { + presets: [ + { + presets: [], + theme: { colors: { - black: 'black', - red: '#ee0000', + transparent: 'transparent', + red: '#ff0000', }, - backgroundColor: (theme) => theme('colors'), }, }, - corePlugins: ['backgroundColor'], + ], + theme: { + extend: { + colors: { + black: 'black', + red: '#ee0000', + }, + backgroundColor: (theme) => theme('colors'), + }, }, - ], - 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`
` }], - 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`
` }], + 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; + } + `) }) }) diff --git a/tests/dark-mode.test.js b/tests/dark-mode.test.js index 6fbea28d915b..613a6b4a1416 100644 --- a/tests/dark-mode.test.js +++ b/tests/dark-mode.test.js @@ -1,321 +1,293 @@ -import { crosscheck, 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`
` }], - 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(.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`
` }], - 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; - } - `) - }) +import { run, html, css, defaults } from './util/run' + +it('should be possible to use the darkMode "class" mode', () => { + let config = { + darkMode: 'class', + content: [{ raw: html`
` }], + 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(.dark .dark\:font-bold) { + font-weight: 700; + } + `) }) +}) - it('should be possible to use the darkMode "media" mode', () => { - let config = { - darkMode: 'media', - content: [{ raw: html`
` }], - 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`
` }], + 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 default to the `media` mode when no mode is provided', () => { - let config = { - content: [{ raw: html`
` }], - 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`
` }], + 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`
` }], - 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`
` }], + 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`
` }], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - :is(.dark .dark\:font-bold) { +it('should default to the `media` mode when mode is set to `false`', () => { + let config = { + darkMode: false, + content: [{ raw: html`
` }], + 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`
` }], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind utilities; - ` +it('should support the deprecated `class` dark mode behavior', () => { + let config = { + darkMode: 'class', + content: [{ raw: html`
` }], + 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; + } + `) + }) +}) - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - :is(.my-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`
` }], + 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`
`, - }, - ], - 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`
`, + }, + ], + 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`
`, - }, - ], - 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`
`, + }, + ], + 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`
` }], - 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`
` }], + 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`
` }], - 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`
` }], + 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`
` }], - 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`
` }], + 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; + } + `) }) }) diff --git a/tests/defaultConfig.test.js b/tests/defaultConfig.test.js index 4e7ca558b14f..9795e699f8a8 100644 --- a/tests/defaultConfig.test.js +++ b/tests/defaultConfig.test.js @@ -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) }) diff --git a/tests/defaultTheme.test.js b/tests/defaultTheme.test.js index c6356b89acdd..4fec1681fd45 100644 --- a/tests/defaultTheme.test.js +++ b/tests/defaultTheme.test.js @@ -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) }) diff --git a/tests/detect-nesting.test.js b/tests/detect-nesting.test.js index 39af6d422c95..08eeacf98464 100644 --- a/tests/detect-nesting.test.js +++ b/tests/detect-nesting.test.js @@ -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`
` }], - } +it('should warn when we detect nested css', () => { + let config = { + content: [{ raw: html`
` }], + } - 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'), - }, - ]) - }) + } + ` + + 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`
` }], + } - it('should not warn when we detect nested css inside css @layer rules', () => { - let config = { - content: [{ raw: html`
` }], + let input = css` + @layer tw-base, tw-components, tw-utilities; + @layer tw-utilities { + @tailwind utilities; } + ` - let input = css` + 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; - } + .underline { + text-decoration-line: underline; } - `) - expect(result.messages).toHaveLength(0) - }) + } + `) + expect(result.messages).toHaveLength(0) }) +}) - it('should warn when we detect namespaced @tailwind at rules', () => { - let config = { - content: [{ raw: html`
` }], - } +it('should warn when we detect namespaced @tailwind at rules', () => { + let config = { + content: [{ raw: html`
` }], + } - 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'), - }, - ]) - }) + 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`
` }], - } +it('should not warn when nesting a single rule inside a media query', () => { + let config = { + content: [{ raw: html`
` }], + } - let input = css` - @tailwind utilities; + let input = css` + @tailwind utilities; - @media (min-width: 768px) { - .nested { - } + @media (min-width: 768px) { + .nested { } - ` + } + ` - return run(input, config).then((result) => { - expect(result.messages).toHaveLength(0) - expect(result.messages).toEqual([]) - }) + 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`
` }], - } - - let input = css` - @tailwind utilities; +it('should only warn for the first detected nesting ', () => { + let config = { + content: [{ raw: html`
` }], + } - .nested { - .example { - } + let input = css` + @tailwind utilities; - .other { - } + .nested { + .example { } .other { - .example { - } } - ` + } + + .other { + .example { + } + } + ` - return run(input, config).then((result) => { - expect(result.messages).toHaveLength(1) - }) + return run(input, config).then((result) => { + expect(result.messages).toHaveLength(1) }) }) diff --git a/tests/escapeClassName.test.js b/tests/escapeClassName.test.js index cdd834a79df7..7a8cd7b7be9d 100644 --- a/tests/escapeClassName.test.js +++ b/tests/escapeClassName.test.js @@ -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') }) diff --git a/tests/evaluateTailwindFunctions.test.js b/tests/evaluateTailwindFunctions.test.js index 5981656ca368..3c9c0c2384b6 100644 --- a/tests/evaluateTailwindFunctions.test.js +++ b/tests/evaluateTailwindFunctions.test.js @@ -2,7 +2,7 @@ import fs from 'fs' import path from 'path' import postcss from 'postcss' import plugin from '../src/lib/evaluateTailwindFunctions' -import { crosscheck, run as runFull, html, css } from './util/run' +import { run as runFull, html, css } from './util/run' function run(input, opts = {}) { return postcss([ @@ -15,1404 +15,1399 @@ function run(input, opts = {}) { ]).process(input, { from: undefined }) } -crosscheck(({ stable, oxide }) => { - test('it looks up values in the theme using dot notation', () => { - let input = css` - .banana { - color: theme('colors.yellow'); - } - ` +test('it looks up values in the theme using dot notation', () => { + let input = css` + .banana { + color: theme('colors.yellow'); + } + ` - let output = css` - .banana { - color: #f7cc50; - } - ` + let output = css` + .banana { + color: #f7cc50; + } + ` - return run(input, { - theme: { - colors: { - yellow: '#f7cc50', - }, + return run(input, { + theme: { + colors: { + yellow: '#f7cc50', }, - }).then((result) => { - expect(result.css).toEqual(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toEqual(output) + expect(result.warnings().length).toBe(0) }) +}) - test('it looks up values in the theme using bracket notation', () => { - let input = css` - .banana { - color: theme('colors[yellow]'); - } - ` +test('it looks up values in the theme using bracket notation', () => { + let input = css` + .banana { + color: theme('colors[yellow]'); + } + ` - let output = css` - .banana { - color: #f7cc50; - } - ` + let output = css` + .banana { + color: #f7cc50; + } + ` - return run(input, { - theme: { - colors: { - yellow: '#f7cc50', - }, + return run(input, { + theme: { + colors: { + yellow: '#f7cc50', }, - }).then((result) => { - expect(result.css).toEqual(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toEqual(output) + expect(result.warnings().length).toBe(0) }) +}) - test('it looks up values in the theme using consecutive bracket notation', () => { - let input = css` - .banana { - color: theme('colors[yellow][100]'); - } - ` +test('it looks up values in the theme using consecutive bracket notation', () => { + let input = css` + .banana { + color: theme('colors[yellow][100]'); + } + ` - let output = css` - .banana { - color: #f7cc50; - } - ` + let output = css` + .banana { + color: #f7cc50; + } + ` - return run(input, { - theme: { - colors: { - yellow: { - 100: '#f7cc50', - }, + return run(input, { + theme: { + colors: { + yellow: { + 100: '#f7cc50', }, }, - }).then((result) => { - expect(result.css).toEqual(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toEqual(output) + expect(result.warnings().length).toBe(0) }) +}) - test('it looks up values in the theme using bracket notation that have dots in them', () => { - let input = css` - .banana { - padding-top: theme('spacing[1.5]'); - } - ` +test('it looks up values in the theme using bracket notation that have dots in them', () => { + let input = css` + .banana { + padding-top: theme('spacing[1.5]'); + } + ` - let output = css` - .banana { - padding-top: 0.375rem; - } - ` + let output = css` + .banana { + padding-top: 0.375rem; + } + ` - return run(input, { - theme: { - spacing: { - '1.5': '0.375rem', - }, + return run(input, { + theme: { + spacing: { + '1.5': '0.375rem', }, - }).then((result) => { - expect(result.css).toEqual(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toEqual(output) + expect(result.warnings().length).toBe(0) }) +}) - test('theme with mismatched brackets throws an error ', async () => { - let config = { - theme: { - spacing: { - '1.5': '0.375rem', - }, +test('theme with mismatched brackets throws an error ', async () => { + let config = { + theme: { + spacing: { + '1.5': '0.375rem', }, + }, + } + + let input = (path) => css` + .banana { + padding-top: theme('${path}'); } + ` - let input = (path) => css` - .banana { - padding-top: theme('${path}'); - } - ` + await expect(run(input('spacing[1.5]]'), config)).rejects.toThrowError( + `Path is invalid. Has unbalanced brackets: spacing[1.5]]` + ) - await expect(run(input('spacing[1.5]]'), config)).rejects.toThrowError( - `Path is invalid. Has unbalanced brackets: spacing[1.5]]` - ) + await expect(run(input('spacing[[1.5]'), config)).rejects.toThrowError( + `Path is invalid. Has unbalanced brackets: spacing[[1.5]` + ) - await expect(run(input('spacing[[1.5]'), config)).rejects.toThrowError( - `Path is invalid. Has unbalanced brackets: spacing[[1.5]` - ) + await expect(run(input('spacing[a['), config)).rejects.toThrowError( + `Path is invalid. Has unbalanced brackets: spacing[a[` + ) +}) - await expect(run(input('spacing[a['), config)).rejects.toThrowError( - `Path is invalid. Has unbalanced brackets: spacing[a[` - ) - }) +test('color can be a function', () => { + let input = css` + .backgroundColor { + color: theme('backgroundColor.fn'); + } + .borderColor { + color: theme('borderColor.fn'); + } + .caretColor { + color: theme('caretColor.fn'); + } + .colors { + color: theme('colors.fn'); + } + .divideColor { + color: theme('divideColor.fn'); + } + .fill { + color: theme('fill.fn'); + } + .gradientColorStops { + color: theme('gradientColorStops.fn'); + } + .placeholderColor { + color: theme('placeholderColor.fn'); + } + .ringColor { + color: theme('ringColor.fn'); + } + .ringOffsetColor { + color: theme('ringOffsetColor.fn'); + } + .stroke { + color: theme('stroke.fn'); + } + .textColor { + color: theme('textColor.fn'); + } + ` - test('color can be a function', () => { - let input = css` - .backgroundColor { - color: theme('backgroundColor.fn'); - } - .borderColor { - color: theme('borderColor.fn'); - } - .caretColor { - color: theme('caretColor.fn'); - } - .colors { - color: theme('colors.fn'); - } - .divideColor { - color: theme('divideColor.fn'); - } - .fill { - color: theme('fill.fn'); - } - .gradientColorStops { - color: theme('gradientColorStops.fn'); - } - .placeholderColor { - color: theme('placeholderColor.fn'); - } - .ringColor { - color: theme('ringColor.fn'); - } - .ringOffsetColor { - color: theme('ringOffsetColor.fn'); - } - .stroke { - color: theme('stroke.fn'); - } - .textColor { - color: theme('textColor.fn'); - } - ` + let output = css` + .backgroundColor { + color: #f00; + } + .borderColor { + color: #f00; + } + .caretColor { + color: #f00; + } + .colors { + color: #f00; + } + .divideColor { + color: #f00; + } + .fill { + color: #f00; + } + .gradientColorStops { + color: #f00; + } + .placeholderColor { + color: #f00; + } + .ringColor { + color: #f00; + } + .ringOffsetColor { + color: #f00; + } + .stroke { + color: #f00; + } + .textColor { + color: #f00; + } + ` - let output = css` - .backgroundColor { - color: #f00; - } - .borderColor { - color: #f00; - } - .caretColor { - color: #f00; - } - .colors { - color: #f00; - } - .divideColor { - color: #f00; - } - .fill { - color: #f00; - } - .gradientColorStops { - color: #f00; - } - .placeholderColor { - color: #f00; - } - .ringColor { - color: #f00; - } - .ringOffsetColor { - color: #f00; - } - .stroke { - color: #f00; - } - .textColor { - color: #f00; - } - ` + let fn = () => `#f00` + + return run(input, { + theme: { + backgroundColor: { fn }, + borderColor: { fn }, + caretColor: { fn }, + colors: { fn }, + divideColor: { fn }, + fill: { fn }, + gradientColorStops: { fn }, + placeholderColor: { fn }, + ringColor: { fn }, + ringOffsetColor: { fn }, + stroke: { fn }, + textColor: { fn }, + }, + }).then((result) => { + expect(result.css).toEqual(output) + expect(result.warnings().length).toBe(0) + }) +}) - let fn = () => `#f00` +test('quotes are optional around the lookup path', () => { + let input = css` + .banana { + color: theme(colors.yellow); + } + ` - return run(input, { - theme: { - backgroundColor: { fn }, - borderColor: { fn }, - caretColor: { fn }, - colors: { fn }, - divideColor: { fn }, - fill: { fn }, - gradientColorStops: { fn }, - placeholderColor: { fn }, - ringColor: { fn }, - ringOffsetColor: { fn }, - stroke: { fn }, - textColor: { fn }, + let output = css` + .banana { + color: #f7cc50; + } + ` + + return run(input, { + theme: { + colors: { + yellow: '#f7cc50', }, - }).then((result) => { - expect(result.css).toEqual(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toEqual(output) + expect(result.warnings().length).toBe(0) }) +}) - test('quotes are optional around the lookup path', () => { - let input = css` - .banana { - color: theme(colors.yellow); - } - ` +test('a default value can be provided', () => { + let input = css` + .cookieMonster { + color: theme('colors.blue', #0000ff); + } + ` - let output = css` - .banana { - color: #f7cc50; - } - ` + let output = css` + .cookieMonster { + color: #0000ff; + } + ` - return run(input, { - theme: { - colors: { - yellow: '#f7cc50', - }, + return run(input, { + theme: { + colors: { + yellow: '#f7cc50', }, - }).then((result) => { - expect(result.css).toEqual(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toEqual(output) + expect(result.warnings().length).toBe(0) }) +}) - test('a default value can be provided', () => { - let input = css` - .cookieMonster { - color: theme('colors.blue', #0000ff); - } - ` +test('the default value can use the theme function', () => { + let input = css` + .cookieMonster { + color: theme('colors.blue', theme('colors.yellow')); + } + ` - let output = css` - .cookieMonster { - color: #0000ff; - } - ` + let output = css` + .cookieMonster { + color: #f7cc50; + } + ` - return run(input, { - theme: { - colors: { - yellow: '#f7cc50', - }, + return run(input, { + theme: { + colors: { + yellow: '#f7cc50', }, - }).then((result) => { - expect(result.css).toEqual(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toEqual(output) + expect(result.warnings().length).toBe(0) }) +}) - test('the default value can use the theme function', () => { - let input = css` - .cookieMonster { - color: theme('colors.blue', theme('colors.yellow')); - } - ` +test('quotes are preserved around default values', () => { + let input = css` + .heading { + font-family: theme('fontFamily.sans', 'Helvetica Neue'); + } + ` - let output = css` - .cookieMonster { - color: #f7cc50; - } - ` + let output = css` + .heading { + font-family: 'Helvetica Neue'; + } + ` - return run(input, { - theme: { - colors: { - yellow: '#f7cc50', - }, + return run(input, { + theme: { + fontFamily: { + serif: 'Constantia', }, - }).then((result) => { - expect(result.css).toEqual(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toEqual(output) + expect(result.warnings().length).toBe(0) }) +}) - test('quotes are preserved around default values', () => { - let input = css` - .heading { - font-family: theme('fontFamily.sans', 'Helvetica Neue'); - } - ` +test('an unquoted list is valid as a default value', () => { + let input = css` + .heading { + font-family: theme('fontFamily.sans', Helvetica, Arial, sans-serif); + } + ` - let output = css` - .heading { - font-family: 'Helvetica Neue'; - } - ` + let output = css` + .heading { + font-family: Helvetica, Arial, sans-serif; + } + ` - return run(input, { - theme: { - fontFamily: { - serif: 'Constantia', - }, + return run(input, { + theme: { + fontFamily: { + serif: 'Constantia', }, - }).then((result) => { - expect(result.css).toEqual(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toEqual(output) + expect(result.warnings().length).toBe(0) }) +}) - test('an unquoted list is valid as a default value', () => { - let input = css` - .heading { - font-family: theme('fontFamily.sans', Helvetica, Arial, sans-serif); - } - ` - - let output = css` - .heading { - font-family: Helvetica, Arial, sans-serif; - } - ` +test('a missing root theme value throws', () => { + let input = css` + .heading { + color: theme('colours.gray.100'); + } + ` - return run(input, { + return expect( + run(input, { theme: { - fontFamily: { - serif: 'Constantia', + colors: { + yellow: '#f7cc50', }, }, - }).then((result) => { - expect(result.css).toEqual(output) - expect(result.warnings().length).toBe(0) }) - }) - - test('a missing root theme value throws', () => { - let input = css` - .heading { - color: theme('colours.gray.100'); - } - ` - - return expect( - run(input, { - theme: { - colors: { - yellow: '#f7cc50', - }, - }, - }) - ).rejects.toThrowError( - `'colours.gray.100' does not exist in your theme config. Your theme has the following top-level keys: 'colors'` - ) - }) - - test('a missing nested theme property throws', () => { - let input = css` - .heading { - color: theme('colors.red'); - } - ` - - return expect( - run(input, { - theme: { - colors: { - blue: 'blue', - yellow: '#f7cc50', - }, - }, - }) - ).rejects.toThrowError( - `'colors.red' does not exist in your theme config. 'colors' has the following valid keys: 'blue', 'yellow'` - ) - }) - - test('a missing nested theme property with a close alternative throws with a suggestion', () => { - let input = css` - .heading { - color: theme('colors.yellw'); - } - ` - - return expect( - run(input, { - theme: { - colors: { - yellow: '#f7cc50', - }, - }, - }) - ).rejects.toThrowError( - `'colors.yellw' does not exist in your theme config. Did you mean 'colors.yellow'?` - ) - }) - - test('a path through a non-object throws', () => { - let input = css` - .heading { - color: theme('colors.yellow.100'); - } - ` - - return expect( - run(input, { - theme: { - colors: { - yellow: '#f7cc50', - }, - }, - }) - ).rejects.toThrowError( - `'colors.yellow.100' does not exist in your theme config. 'colors.yellow' is not an object.` - ) - }) + ).rejects.toThrowError( + `'colours.gray.100' does not exist in your theme config. Your theme has the following top-level keys: 'colors'` + ) +}) - test('a path which exists but is not a string throws', () => { - let input = css` - .heading { - color: theme('colors.yellow'); - } - ` +test('a missing nested theme property throws', () => { + let input = css` + .heading { + color: theme('colors.red'); + } + ` - return expect( - run(input, { - theme: { - colors: { - yellow: Symbol(), - }, + return expect( + run(input, { + theme: { + colors: { + blue: 'blue', + yellow: '#f7cc50', }, - }) - ).rejects.toThrowError(`'colors.yellow' was found but does not resolve to a string.`) - }) + }, + }) + ).rejects.toThrowError( + `'colors.red' does not exist in your theme config. 'colors' has the following valid keys: 'blue', 'yellow'` + ) +}) - test('a path which exists but is invalid throws', () => { - let input = css` - .heading { - color: theme('colors'); - } - ` +test('a missing nested theme property with a close alternative throws with a suggestion', () => { + let input = css` + .heading { + color: theme('colors.yellw'); + } + ` - return expect( - run(input, { - theme: { - colors: {}, + return expect( + run(input, { + theme: { + colors: { + yellow: '#f7cc50', }, - }) - ).rejects.toThrowError(`'colors' was found but does not resolve to a string.`) - }) + }, + }) + ).rejects.toThrowError( + `'colors.yellw' does not exist in your theme config. Did you mean 'colors.yellow'?` + ) +}) - test('a path which is an object throws with a suggested key', () => { - let input = css` - .heading { - color: theme('colors'); - } - ` +test('a path through a non-object throws', () => { + let input = css` + .heading { + color: theme('colors.yellow.100'); + } + ` - return expect( - run(input, { - theme: { - colors: { - yellow: '#f7cc50', - }, + return expect( + run(input, { + theme: { + colors: { + yellow: '#f7cc50', }, - }) - ).rejects.toThrowError( - `'colors' was found but does not resolve to a string. Did you mean something like 'colors.yellow'?` - ) - }) - - test('array values are joined by default', () => { - let input = css` - .heading { - font-family: theme('fontFamily.sans'); - } - ` + }, + }) + ).rejects.toThrowError( + `'colors.yellow.100' does not exist in your theme config. 'colors.yellow' is not an object.` + ) +}) - let output = css` - .heading { - font-family: Inter, Helvetica, sans-serif; - } - ` +test('a path which exists but is not a string throws', () => { + let input = css` + .heading { + color: theme('colors.yellow'); + } + ` - return run(input, { + return expect( + run(input, { theme: { - fontFamily: { - sans: ['Inter', 'Helvetica', 'sans-serif'], + colors: { + yellow: Symbol(), }, }, - }).then((result) => { - expect(result.css).toEqual(output) - expect(result.warnings().length).toBe(0) }) - }) - - test('font sizes are retrieved without default line-heights or letter-spacing', () => { - let input = css` - .heading-1 { - font-size: theme('fontSize.lg'); - } - .heading-2 { - font-size: theme('fontSize.xl'); - } - ` + ).rejects.toThrowError(`'colors.yellow' was found but does not resolve to a string.`) +}) - let output = css` - .heading-1 { - font-size: 20px; - } - .heading-2 { - font-size: 24px; - } - ` +test('a path which exists but is invalid throws', () => { + let input = css` + .heading { + color: theme('colors'); + } + ` - return run(input, { + return expect( + run(input, { theme: { - fontSize: { - lg: ['20px', '28px'], - xl: ['24px', { lineHeight: '32px', letterSpacing: '-0.01em' }], - }, + colors: {}, }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) }) - }) - - test('outlines are retrieved without default outline-offset', () => { - let input = css` - .element { - outline: theme('outline.black'); - } - ` + ).rejects.toThrowError(`'colors' was found but does not resolve to a string.`) +}) - let output = css` - .element { - outline: 2px dotted #000; - } - ` +test('a path which is an object throws with a suggested key', () => { + let input = css` + .heading { + color: theme('colors'); + } + ` - return run(input, { + return expect( + run(input, { theme: { - outline: { - black: ['2px dotted black', '4px'], + colors: { + yellow: '#f7cc50', }, }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) }) - }) + ).rejects.toThrowError( + `'colors' was found but does not resolve to a string. Did you mean something like 'colors.yellow'?` + ) +}) - test('font-family values are retrieved without font-variation-settings', () => { - let input = css` - .heading-1 { - font-family: theme('fontFamily.sans'); - } - .heading-2 { - font-family: theme('fontFamily.serif'); - } - .heading-3 { - font-family: theme('fontFamily.mono'); - } - ` +test('array values are joined by default', () => { + let input = css` + .heading { + font-family: theme('fontFamily.sans'); + } + ` - let output = css` - .heading-1 { - font-family: Inter; - } - .heading-2 { - font-family: Times, serif; - } - .heading-3 { - font-family: Menlo, monospace; - } - ` + let output = css` + .heading { + font-family: Inter, Helvetica, sans-serif; + } + ` - return run(input, { - theme: { - fontFamily: { - sans: ['Inter', { fontVariationSettings: '"opsz" 32' }], - serif: [['Times', 'serif'], { fontVariationSettings: '"opsz" 32' }], - mono: ['Menlo, monospace', { fontVariationSettings: '"opsz" 32' }], - }, + return run(input, { + theme: { + fontFamily: { + sans: ['Inter', 'Helvetica', 'sans-serif'], }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toEqual(output) + expect(result.warnings().length).toBe(0) }) +}) - test('font-variation-settings values can be retrieved', () => { - let input = css` - .heading { - font-family: theme('fontFamily.sans'); - font-variation-settings: theme('fontFamily.sans[1].fontVariationSettings'); - } - ` +test('font sizes are retrieved without default line-heights or letter-spacing', () => { + let input = css` + .heading-1 { + font-size: theme('fontSize.lg'); + } + .heading-2 { + font-size: theme('fontSize.xl'); + } + ` - let output = css` - .heading { - font-family: Inter; - font-variation-settings: 'opsz' 32; - } - ` + let output = css` + .heading-1 { + font-size: 20px; + } + .heading-2 { + font-size: 24px; + } + ` - return run(input, { - theme: { - fontFamily: { - sans: ['Inter', { fontVariationSettings: "'opsz' 32" }], - }, + return run(input, { + theme: { + fontSize: { + lg: ['20px', '28px'], + xl: ['24px', { lineHeight: '32px', letterSpacing: '-0.01em' }], }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('font-family values are joined when an array', () => { - let input = css` - .element { - font-family: theme('fontFamily.sans'); - } - ` +test('outlines are retrieved without default outline-offset', () => { + let input = css` + .element { + outline: theme('outline.black'); + } + ` - let output = css` - .element { - font-family: Helvetica, Arial, sans-serif; - } - ` + let output = css` + .element { + outline: 2px dotted #000; + } + ` - return run(input, { - theme: { - fontFamily: { - sans: ['Helvetica', 'Arial', 'sans-serif'], - }, + return run(input, { + theme: { + outline: { + black: ['2px dotted black', '4px'], }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('font-family values are retrieved without font-feature-settings', () => { - let input = css` - .heading-1 { - font-family: theme('fontFamily.sans'); - } - .heading-2 { - font-family: theme('fontFamily.serif'); - } - .heading-3 { - font-family: theme('fontFamily.mono'); - } - ` +test('font-family values are retrieved without font-variation-settings', () => { + let input = css` + .heading-1 { + font-family: theme('fontFamily.sans'); + } + .heading-2 { + font-family: theme('fontFamily.serif'); + } + .heading-3 { + font-family: theme('fontFamily.mono'); + } + ` - let output = css` - .heading-1 { - font-family: Inter; - } - .heading-2 { - font-family: Times, serif; - } - .heading-3 { - font-family: Menlo, monospace; - } - ` + let output = css` + .heading-1 { + font-family: Inter; + } + .heading-2 { + font-family: Times, serif; + } + .heading-3 { + font-family: Menlo, monospace; + } + ` - return run(input, { - theme: { - fontFamily: { - sans: ['Inter', { fontFeatureSettings: '"cv11"' }], - serif: [['Times', 'serif'], { fontFeatureSettings: '"cv11"' }], - mono: ['Menlo, monospace', { fontFeatureSettings: '"cv11"' }], - }, + return run(input, { + theme: { + fontFamily: { + sans: ['Inter', { fontVariationSettings: '"opsz" 32' }], + serif: [['Times', 'serif'], { fontVariationSettings: '"opsz" 32' }], + mono: ['Menlo, monospace', { fontVariationSettings: '"opsz" 32' }], }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('font-feature-settings values can be retrieved', () => { - let input = css` - .heading { - font-family: theme('fontFamily.sans'); - font-feature-settings: theme('fontFamily.sans[1].fontFeatureSettings'); - } - ` +test('font-variation-settings values can be retrieved', () => { + let input = css` + .heading { + font-family: theme('fontFamily.sans'); + font-variation-settings: theme('fontFamily.sans[1].fontVariationSettings'); + } + ` - let output = css` - .heading { - font-feature-settings: 'cv11'; - font-family: Inter; - } - ` + let output = css` + .heading { + font-family: Inter; + font-variation-settings: 'opsz' 32; + } + ` - return run(input, { - theme: { - fontFamily: { - sans: ['Inter', { fontFeatureSettings: "'cv11'" }], - }, + return run(input, { + theme: { + fontFamily: { + sans: ['Inter', { fontVariationSettings: "'opsz' 32" }], }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('box-shadow values are joined when an array', () => { - let input = css` - .element { - box-shadow: theme('boxShadow.wtf'); - } - ` +test('font-family values are joined when an array', () => { + let input = css` + .element { + font-family: theme('fontFamily.sans'); + } + ` - let output = css` - .element { - box-shadow: 0 0 2px #000, 1px 2px 3px #fff; - } - ` + let output = css` + .element { + font-family: Helvetica, Arial, sans-serif; + } + ` - return run(input, { - theme: { - boxShadow: { - wtf: ['0 0 2px black', '1px 2px 3px white'], - }, + return run(input, { + theme: { + fontFamily: { + sans: ['Helvetica', 'Arial', 'sans-serif'], }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('transition-property values are joined when an array', () => { - let input = css` - .element { - transition-property: theme('transitionProperty.colors'); - } - ` +test('font-family values are retrieved without font-feature-settings', () => { + let input = css` + .heading-1 { + font-family: theme('fontFamily.sans'); + } + .heading-2 { + font-family: theme('fontFamily.serif'); + } + .heading-3 { + font-family: theme('fontFamily.mono'); + } + ` - let output = css` - .element { - transition-property: color, fill; - } - ` + let output = css` + .heading-1 { + font-family: Inter; + } + .heading-2 { + font-family: Times, serif; + } + .heading-3 { + font-family: Menlo, monospace; + } + ` - return run(input, { - theme: { - transitionProperty: { - colors: ['color', 'fill'], - }, + return run(input, { + theme: { + fontFamily: { + sans: ['Inter', { fontFeatureSettings: '"cv11"' }], + serif: [['Times', 'serif'], { fontFeatureSettings: '"cv11"' }], + mono: ['Menlo, monospace', { fontFeatureSettings: '"cv11"' }], }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('transition-duration values are joined when an array', () => { - let input = css` - .element { - transition-duration: theme('transitionDuration.lol'); - } - ` +test('font-feature-settings values can be retrieved', () => { + let input = css` + .heading { + font-family: theme('fontFamily.sans'); + font-feature-settings: theme('fontFamily.sans[1].fontFeatureSettings'); + } + ` - let output = css` - .element { - transition-duration: 1s, 2s; - } - ` + let output = css` + .heading { + font-feature-settings: 'cv11'; + font-family: Inter; + } + ` - return run(input, { - theme: { - transitionDuration: { - lol: ['1s', '2s'], - }, + return run(input, { + theme: { + fontFamily: { + sans: ['Inter', { fontFeatureSettings: "'cv11'" }], }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('basic screen function calls are expanded', () => { - let input = css` - @media screen(sm) { - .foo { - color: red; - } - } - ` +test('box-shadow values are joined when an array', () => { + let input = css` + .element { + box-shadow: theme('boxShadow.wtf'); + } + ` - let output = css` - @media (min-width: 600px) { - .foo { - color: red; - } - } - ` + let output = css` + .element { + box-shadow: 0 0 2px #000, 1px 2px 3px #fff; + } + ` - return run(input, { - theme: { screens: { sm: '600px' } }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + return run(input, { + theme: { + boxShadow: { + wtf: ['0 0 2px black', '1px 2px 3px white'], + }, + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('screen function supports max-width screens', () => { - let input = css` - @media screen(sm) { - .foo { - color: red; - } - } - ` +test('transition-property values are joined when an array', () => { + let input = css` + .element { + transition-property: theme('transitionProperty.colors'); + } + ` - let output = css` - @media (max-width: 600px) { - .foo { - color: red; - } - } - ` + let output = css` + .element { + transition-property: color, fill; + } + ` - return run(input, { - theme: { screens: { sm: { max: '600px' } } }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + return run(input, { + theme: { + transitionProperty: { + colors: ['color', 'fill'], + }, + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('screen function supports min-width screens', () => { - let input = css` - @media screen(sm) { - .foo { - color: red; - } - } - ` +test('transition-duration values are joined when an array', () => { + let input = css` + .element { + transition-duration: theme('transitionDuration.lol'); + } + ` - let output = css` - @media (min-width: 600px) { - .foo { - color: red; - } - } - ` + let output = css` + .element { + transition-duration: 1s, 2s; + } + ` - return run(input, { - theme: { screens: { sm: { min: '600px' } } }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + return run(input, { + theme: { + transitionDuration: { + lol: ['1s', '2s'], + }, + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('screen function supports min-width and max-width screens', () => { - let input = css` - @media screen(sm) { - .foo { - color: red; - } +test('basic screen function calls are expanded', () => { + let input = css` + @media screen(sm) { + .foo { + color: red; } - ` + } + ` - let output = css` - @media (min-width: 600px) and (max-width: 700px) { - .foo { - color: red; - } + let output = css` + @media (min-width: 600px) { + .foo { + color: red; } - ` + } + ` - return run(input, { - theme: { screens: { sm: { min: '600px', max: '700px' } } }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + return run(input, { + theme: { screens: { sm: '600px' } }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('screen function supports raw screens', () => { - let input = css` - @media screen(mono) { - .foo { - color: red; - } +test('screen function supports max-width screens', () => { + let input = css` + @media screen(sm) { + .foo { + color: red; } - ` + } + ` - let output = css` - @media monochrome { - .foo { - color: red; - } + let output = css` + @media (max-width: 600px) { + .foo { + color: red; } - ` + } + ` - return run(input, { - theme: { screens: { mono: { raw: 'monochrome' } } }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + return run(input, { + theme: { screens: { sm: { max: '600px' } } }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('screen arguments can be quoted', () => { - let input = css` - @media screen('sm') { - .foo { - color: red; - } +test('screen function supports min-width screens', () => { + let input = css` + @media screen(sm) { + .foo { + color: red; } - ` + } + ` - let output = css` - @media (min-width: 600px) { - .foo { - color: red; - } + let output = css` + @media (min-width: 600px) { + .foo { + color: red; } - ` + } + ` - return run(input, { - theme: { screens: { sm: '600px' } }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + return run(input, { + theme: { screens: { sm: { min: '600px' } } }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('Theme function can extract alpha values for colors (1)', () => { - let input = css` +test('screen function supports min-width and max-width screens', () => { + let input = css` + @media screen(sm) { .foo { - color: theme(colors.blue.500 / 50%); + color: red; } - ` + } + ` - let output = css` + let output = css` + @media (min-width: 600px) and (max-width: 700px) { .foo { - color: #3b82f680; + color: red; } - ` + } + ` - return run(input, { - theme: { - colors: { blue: { 500: '#3b82f6' } }, - }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + return run(input, { + theme: { screens: { sm: { min: '600px', max: '700px' } } }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('Theme function can extract alpha values for colors (2)', () => { - let input = css` +test('screen function supports raw screens', () => { + let input = css` + @media screen(mono) { .foo { - color: theme(colors.blue.500 / 0.5); + color: red; } - ` + } + ` - let output = css` + let output = css` + @media monochrome { .foo { - color: #3b82f680; + color: red; } - ` + } + ` - return run(input, { - theme: { - colors: { blue: { 500: '#3b82f6' } }, - }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + return run(input, { + theme: { screens: { mono: { raw: 'monochrome' } } }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('Theme function can extract alpha values for colors (3)', () => { - let input = css` +test('screen arguments can be quoted', () => { + let input = css` + @media screen('sm') { .foo { - color: theme(colors.blue.500 / var(--my-alpha)); + color: red; } - ` + } + ` - let output = css` + let output = css` + @media (min-width: 600px) { .foo { - color: rgb(59 130 246 / var(--my-alpha)); + color: red; } - ` + } + ` - return run(input, { - theme: { - colors: { blue: { 500: '#3b82f6' } }, - }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + return run(input, { + theme: { screens: { sm: '600px' } }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) + +test('Theme function can extract alpha values for colors (1)', () => { + let input = css` + .foo { + color: theme(colors.blue.500 / 50%); + } + ` + + let output = css` + .foo { + color: #3b82f680; + } + ` + + return run(input, { + theme: { + colors: { blue: { 500: '#3b82f6' } }, + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) + +test('Theme function can extract alpha values for colors (2)', () => { + let input = css` + .foo { + color: theme(colors.blue.500 / 0.5); + } + ` + + let output = css` + .foo { + color: #3b82f680; + } + ` + + return run(input, { + theme: { + colors: { blue: { 500: '#3b82f6' } }, + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) + +test('Theme function can extract alpha values for colors (3)', () => { + let input = css` + .foo { + color: theme(colors.blue.500 / var(--my-alpha)); + } + ` + + let output = css` + .foo { + color: rgb(59 130 246 / var(--my-alpha)); + } + ` + + return run(input, { + theme: { + colors: { blue: { 500: '#3b82f6' } }, + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('Theme function can extract alpha values for colors (4)', () => { - let input = css` - .foo { - color: theme(colors.blue.500 / 50%); - } - ` +test('Theme function can extract alpha values for colors (4)', () => { + let input = css` + .foo { + color: theme(colors.blue.500 / 50%); + } + ` - let output = css` - .foo { - color: #3c83f680; - } - ` + let output = css` + .foo { + color: #3c83f680; + } + ` - return run(input, { - theme: { - colors: { - blue: { 500: 'hsl(217, 91%, 60%)' }, - }, + return run(input, { + theme: { + colors: { + blue: { 500: 'hsl(217, 91%, 60%)' }, }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('Theme function can extract alpha values for colors (5)', () => { - let input = css` - .foo { - color: theme(colors.blue.500 / 0.5); - } - ` +test('Theme function can extract alpha values for colors (5)', () => { + let input = css` + .foo { + color: theme(colors.blue.500 / 0.5); + } + ` - let output = css` - .foo { - color: #3c83f680; - } - ` + let output = css` + .foo { + color: #3c83f680; + } + ` - return run(input, { - theme: { - colors: { - blue: { 500: 'hsl(217, 91%, 60%)' }, - }, + return run(input, { + theme: { + colors: { + blue: { 500: 'hsl(217, 91%, 60%)' }, }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('Theme function can extract alpha values for colors (6)', () => { - let input = css` - .foo { - color: theme(colors.blue.500 / var(--my-alpha)); - } - ` +test('Theme function can extract alpha values for colors (6)', () => { + let input = css` + .foo { + color: theme(colors.blue.500 / var(--my-alpha)); + } + ` - let output = css` - .foo { - color: hsl(217 91% 60% / var(--my-alpha)); - } - ` + let output = css` + .foo { + color: hsl(217 91% 60% / var(--my-alpha)); + } + ` - return run(input, { - theme: { - colors: { - blue: { 500: 'hsl(217, 91%, 60%)' }, - }, + return run(input, { + theme: { + colors: { + blue: { 500: 'hsl(217, 91%, 60%)' }, }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('Theme function can extract alpha values for colors (7)', () => { - let input = css` - .foo { - color: theme(colors.blue.500 / var(--my-alpha)); - } - ` +test('Theme function can extract alpha values for colors (7)', () => { + let input = css` + .foo { + color: theme(colors.blue.500 / var(--my-alpha)); + } + ` - let output = css` - .foo { - color: rgb(var(--foo) / var(--my-alpha)); - } - ` + let output = css` + .foo { + color: rgb(var(--foo) / var(--my-alpha)); + } + ` - return runFull(input, { - theme: { - colors: { - blue: { - 500: 'rgb(var(--foo) / )', - }, + return runFull(input, { + theme: { + colors: { + blue: { + 500: 'rgb(var(--foo) / )', }, }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('Theme function can extract alpha values for colors (8)', () => { - let input = css` - .foo { - color: theme(colors.blue.500 / theme(opacity.myalpha)); - } - ` +test('Theme function can extract alpha values for colors (8)', () => { + let input = css` + .foo { + color: theme(colors.blue.500 / theme(opacity.myalpha)); + } + ` - let output = css` - .foo { - color: rgb(var(--foo) / 50%); - } - ` + let output = css` + .foo { + color: rgb(var(--foo) / 50%); + } + ` - return runFull(input, { - theme: { - colors: { - blue: { - 500: 'rgb(var(--foo) / )', - }, + return runFull(input, { + theme: { + colors: { + blue: { + 500: 'rgb(var(--foo) / )', }, + }, - opacity: { - myalpha: '50%', - }, + opacity: { + myalpha: '50%', }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('Theme functions replace the alpha value placeholder even with no alpha provided', () => { - let input = css` - .foo { - background: theme(colors.blue.400); - color: theme(colors.blue.500); - } - ` +test('Theme functions replace the alpha value placeholder even with no alpha provided', () => { + let input = css` + .foo { + background: theme(colors.blue.400); + color: theme(colors.blue.500); + } + ` - let output = css` - .foo { - color: rgb(var(--foo) / 1); - background: #00f; - } - ` + let output = css` + .foo { + color: rgb(var(--foo) / 1); + background: #00f; + } + ` - return runFull(input, { - theme: { - colors: { - blue: { - 400: 'rgb(0 0 255 / )', - 500: 'rgb(var(--foo) / )', - }, + return runFull(input, { + theme: { + colors: { + blue: { + 400: 'rgb(0 0 255 / )', + 500: 'rgb(var(--foo) / )', }, }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('Theme functions can reference values with slashes in brackets', () => { - let input = css` - .foo1 { - color: theme(colors[a/b]); - } - .foo2 { - color: theme(colors[a/b]/50%); - } - ` +test('Theme functions can reference values with slashes in brackets', () => { + let input = css` + .foo1 { + color: theme(colors[a/b]); + } + .foo2 { + color: theme(colors[a/b]/50%); + } + ` - let output = css` - .foo1 { - color: #000; - } - .foo2 { - color: #00000080; - } - ` + let output = css` + .foo1 { + color: #000; + } + .foo2 { + color: #00000080; + } + ` - return runFull(input, { - theme: { - colors: { - 'a/b': '#000000', - }, + return runFull(input, { + theme: { + colors: { + 'a/b': '#000000', }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('Theme functions with alpha value inside quotes', () => { - let input = css` - .foo { - color: theme('colors.yellow / 50%'); - } - ` +test('Theme functions with alpha value inside quotes', () => { + let input = css` + .foo { + color: theme('colors.yellow / 50%'); + } + ` - let output = css` - .foo { - color: #f7cc5080; - } - ` + let output = css` + .foo { + color: #f7cc5080; + } + ` - return runFull(input, { - theme: { - colors: { - yellow: '#f7cc50', - }, + return runFull(input, { + theme: { + colors: { + yellow: '#f7cc50', }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('Theme functions with alpha with quotes value around color only', () => { - let input = css` - .foo { - color: theme('colors.yellow' / 50%); - } - ` +test('Theme functions with alpha with quotes value around color only', () => { + let input = css` + .foo { + color: theme('colors.yellow' / 50%); + } + ` - let output = css` - .foo { - color: #f7cc5080; - } - ` + let output = css` + .foo { + color: #f7cc5080; + } + ` - return runFull(input, { - theme: { - colors: { - yellow: '#f7cc50', - }, + return runFull(input, { + theme: { + colors: { + yellow: '#f7cc50', }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) +}) - it('can find values with slashes in the theme key while still allowing for alpha values ', () => { - let input = css` +it('can find values with slashes in the theme key while still allowing for alpha values ', () => { + let input = css` + .foo00 { + color: theme(colors.foo-5); + } + .foo01 { + color: theme(colors.foo-5/10); + } + .foo02 { + color: theme(colors.foo-5/10/25); + } + .foo03 { + color: theme(colors.foo-5 / 10); + } + .foo04 { + color: theme(colors.foo-5/10 / 25); + } + ` + + return runFull(input, { + theme: { + colors: { + 'foo-5': '#050000', + 'foo-5/10': '#051000', + 'foo-5/10/25': '#051025', + }, + }, + }).then((result) => { + expect(result.css).toMatchCss(css` .foo00 { - color: theme(colors.foo-5); + color: #050000; } .foo01 { - color: theme(colors.foo-5/10); + color: #051000; } .foo02 { - color: theme(colors.foo-5/10/25); + color: #051025; } .foo03 { - color: theme(colors.foo-5 / 10); + color: #050000; } .foo04 { - color: theme(colors.foo-5/10 / 25); + color: #051000; } - ` - - return runFull(input, { - theme: { - colors: { - 'foo-5': '#050000', - 'foo-5/10': '#051000', - 'foo-5/10/25': '#051025', - }, - }, - }).then((result) => { - expect(result.css).toMatchCss(css` - .foo00 { - color: #050000; - } - .foo01 { - color: #051000; - } - .foo02 { - color: #051025; - } - .foo03 { - color: #050000; - } - .foo04 { - color: #051000; - } - `) - }) + `) }) +}) - describe('context dependent', () => { - let configPath = path.resolve(__dirname, './evaluate-tailwind-functions.tailwind.config.js') - let filePath = path.resolve(__dirname, './evaluate-tailwind-functions.test.html') - let config = { - content: [filePath], - corePlugins: { preflight: false }, - } +describe('context dependent', () => { + let configPath = path.resolve(__dirname, './evaluate-tailwind-functions.tailwind.config.js') + let filePath = path.resolve(__dirname, './evaluate-tailwind-functions.test.html') + let config = { + content: [filePath], + corePlugins: { preflight: false }, + } + + // Rebuild the config file for each test + beforeEach(() => fs.promises.writeFile(configPath, `module.exports = ${JSON.stringify(config)};`)) + afterEach(() => fs.promises.unlink(configPath)) - // Rebuild the config file for each test - beforeEach(() => - fs.promises.writeFile(configPath, `module.exports = ${JSON.stringify(config)};`) + test('should not generate when theme fn doesnt resolve', async () => { + await fs.promises.writeFile( + filePath, + html` +
+
+ ` ) - afterEach(() => fs.promises.unlink(configPath)) - - oxide.test.todo('should not generate when theme fn doesnt resolve') - stable.test('should not generate when theme fn doesnt resolve', async () => { - await fs.promises.writeFile( - filePath, - html` -
-
- ` - ) - - // TODO: We need a way to reuse the context in our test suite without requiring writing to files - // It should be an explicit thing tho — like we create a context and pass it in or something - let result = await runFull('@tailwind utilities', configPath) - - // 1. On first run it should work because it's been removed from the class cache - expect(result.css).toMatchCss(css` - .underline { - text-decoration-line: underline; - } - `) - // 2. But we get a warning in the console - expect().toHaveBeenWarnedWith(['invalid-theme-key-in-class']) + // TODO: We need a way to reuse the context in our test suite without requiring writing to files + // It should be an explicit thing tho — like we create a context and pass it in or something + let result = await runFull('@tailwind utilities', configPath) - // 3. The second run should work fine because it's been removed from the class cache - result = await runFull('@tailwind utilities', configPath) + // 1. On first run it should work because it's been removed from the class cache + expect(result.css).toMatchCss(css` + .underline { + text-decoration-line: underline; + } + `) - expect(result.css).toMatchCss(css` - .underline { - text-decoration-line: underline; - } - `) + // 2. But we get a warning in the console + expect().toHaveBeenWarnedWith(['invalid-theme-key-in-class']) - // 4. But we've not received any further logs about it - expect().toHaveBeenWarnedWith(['invalid-theme-key-in-class']) - }) + // 3. The second run should work fine because it's been removed from the class cache + result = await runFull('@tailwind utilities', configPath) - test('it works mayhaps', async () => { - let input = css` - .test { - /* prettier-ignore */ - inset: calc(-1 * (2*theme("spacing.4"))); - /* prettier-ignore */ - padding: calc(-1 * (2* theme("spacing.4"))); - } - ` + expect(result.css).toMatchCss(css` + .underline { + text-decoration-line: underline; + } + `) - let output = css` - .test { - /* prettier-ignore */ - inset: calc(-1 * (2*1rem)); - /* prettier-ignore */ - padding: calc(-1 * (2* 1rem)); - } - ` + // 4. But we've not received any further logs about it + expect().toHaveBeenWarnedWith(['invalid-theme-key-in-class']) + }) + + test('it works mayhaps', async () => { + let input = css` + .test { + /* prettier-ignore */ + inset: calc(-1 * (2*theme("spacing.4"))); + /* prettier-ignore */ + padding: calc(-1 * (2* theme("spacing.4"))); + } + ` + + let output = css` + .test { + /* prettier-ignore */ + inset: calc(-1 * (2*1rem)); + /* prettier-ignore */ + padding: calc(-1 * (2* 1rem)); + } + ` - return run(input, { - theme: { - spacing: { - 4: '1rem', - }, + return run(input, { + theme: { + spacing: { + 4: '1rem', }, - }).then((result) => { - expect(result.css).toMatchCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) }) }) }) diff --git a/tests/experimental.test.js b/tests/experimental.test.js index 3497783f9c00..b3fa49185bae 100644 --- a/tests/experimental.test.js +++ b/tests/experimental.test.js @@ -1,197 +1,195 @@ -import { crosscheck, run, html, css } from './util/run' - -crosscheck(() => { - test('experimental universal selector improvements (box-shadow)', () => { - let config = { - experimental: 'all', - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } - - 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); - } - `) - }) +import { run, html, css } from './util/run' + +test('experimental universal selector improvements (box-shadow)', () => { + let config = { + experimental: 'all', + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } + + 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); + } + `) }) +}) - test('experimental universal selector improvements (pseudo hover)', () => { - let config = { - experimental: 'all', - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } - - 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); - } - `) - }) +test('experimental universal selector improvements (pseudo hover)', () => { + let config = { + experimental: 'all', + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } + + 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); + } + `) }) +}) - test('experimental universal selector improvements (multiple classes: group)', () => { - let config = { - experimental: 'all', - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } - - 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); - } - `) - }) +test('experimental universal selector improvements (multiple classes: group)', () => { + let config = { + experimental: 'all', + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } + + 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); + } + `) }) +}) - test('experimental universal selector improvements (child selectors: divide-y)', () => { - let config = { - experimental: 'all', - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } - - 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)); - } - `) - }) +test('experimental universal selector improvements (child selectors: divide-y)', () => { + let config = { + experimental: 'all', + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } + + 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)); + } + `) }) +}) - test('experimental universal selector improvements (hover:divide-y)', () => { - let config = { - experimental: 'all', - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } - - 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)); - } - `) - }) +test('experimental universal selector improvements (hover:divide-y)', () => { + let config = { + experimental: 'all', + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } + + 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)); + } + `) }) +}) - test('experimental universal selector improvements (#app important)', () => { - let config = { - experimental: 'all', - important: '#app', - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } - - 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); - } - `) - }) +test('experimental universal selector improvements (#app important)', () => { + let config = { + experimental: 'all', + important: '#app', + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } + + 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); + } + `) }) }) diff --git a/tests/extractor-edge-cases.test.js b/tests/extractor-edge-cases.test.js index 412702b359b9..83ca99e3778e 100644 --- a/tests/extractor-edge-cases.test.js +++ b/tests/extractor-edge-cases.test.js @@ -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`

">Hello world

`, - }, - ], - } +test('PHP arrays', async () => { + let config = { + content: [ + { + raw: html`

">Hello world

`, + }, + ], + } - 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`
` }] } +test('arbitrary values with quotes', async () => { + let config = { content: [{ raw: html`
` }] } - 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); + } + `) }) }) diff --git a/tests/flattenColorPalette.test.js b/tests/flattenColorPalette.test.js index dae66e806796..e8edcd9aa18a 100644 --- a/tests/flattenColorPalette.test.js +++ b/tests/flattenColorPalette.test.js @@ -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)', + }, }) + ).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', - }, +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', + }, }) + ).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({}) - }) +test('it handles empty objects', () => { + expect(flattenColorPalette({})).toEqual({}) }) diff --git a/tests/format-variant-selector.test.js b/tests/format-variant-selector.test.js index 3ce3d1322c94..21343b58d78e 100644 --- a/tests/format-variant-selector.test.js +++ b/tests/format-variant-selector.test.js @@ -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 }] + let formats = [{ format: '&:hover', respectPrefix: true }] - expect(finalizeSelector(selector, formats, { candidate })).toEqual('.hover\\:text-center:hover') - }) + 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' +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 }, - ] + let formats = [ + { format: '&:hover', respectPrefix: true }, + { format: '&:focus', respectPrefix: true }, + ] - expect(finalizeSelector(selector, formats, { candidate })).toEqual( - '.focus\\:hover\\:text-center:hover:focus' - ) - }) + 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)]' +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 }] + let formats = [{ format: '&:hover', respectPrefix: true }] - expect(finalizeSelector(selector, formats, { candidate })).toEqual( - '.hover\\:bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]:hover' - ) - }) + 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)]' +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 }] + let formats = [{ format: '&:hover', respectPrefix: true }] - expect(finalizeSelector(selector, formats, { candidate })).toEqual( - '.hover\\:bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]:hover' - ) - }) + 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' +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 }] + let formats = [{ format: '&:hover', respectPrefix: true }] - expect(finalizeSelector(selector, formats, { candidate })).toEqual( - '.hover\\:space-x-4:hover > :not([hidden]) ~ :not([hidden])' - ) - }) + 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' +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 }, - ] + 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])' - ) - }) + 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' +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 }] + let formats = [{ format: ':merge(.group):hover &', respectPrefix: true }] - expect(finalizeSelector(selector, formats, { candidate })).toEqual( - '.group:hover .group-hover\\:text-center' - ) - }) + 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' +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 }, - ] + 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' - ) - }) + 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' +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 }] + 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])' - ) - }) + 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' +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 }, - ] + 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])' - ) - }) + 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' +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 ~ &' }, - ] + 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' - ) - }) + 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' +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 }, - ] + 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' - ) - }) + 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' +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 }, - ] + 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' - ) - }) + 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 - ] +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)' + ) +}) - 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('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 - ] +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])' + ) +}) - expect(finalizeSelector(selector, formats, { candidate })).toEqual( - ':where(.group:hover .prose-headings\\:group-hover\\:text-center) :is(h1, h2, h3, h4)' - ) - }) +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 = [] - 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, context })).toEqual('.tw-text-center') +}) - 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 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 }] - 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-foo .foo\\:tw-text-center' + ) +}) - expect(finalizeSelector(selector, formats, { candidate, context })).toEqual('.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 }] - 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( + '.foo .\\[\\.foo_\\&\\]\\:tw-text-center' + ) +}) - expect(finalizeSelector(selector, formats, { candidate, context })).toEqual( - '.tw-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 - 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 }] + 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, context })).toEqual( - '.foo .\\[\\.foo_\\&\\]\\:tw-text-center' + 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('Merged selectors with mixed combinators uses the first one', () => { - // This isn't explicitly specced behavior but it is how it works today + it('example b', () => { + let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])' + let candidate = 'group-hover:hover:space-x-4' - let selector = '.text-center' - let candidate = 'text-center' let formats = [ - { format: ':merge(.group):focus > &', respectPrefix: false }, - { format: ':merge(.group):hover &', respectPrefix: false }, + { format: '&:hover', respectPrefix: true }, + { format: ':merge(.group):hover &', respectPrefix: true }, ] expect(finalizeSelector(selector, formats, { candidate })).toEqual( - '.group:hover:focus > .text-center' + '.group:hover .group-hover\\:hover\\:space-x-4:hover > :not([hidden]) ~ :not([hidden])' ) }) - describe('real examples', () => { - it('example a', () => { - let selector = '.placeholder-red-500::placeholder' - let candidate = 'hover:placeholder-red-500' + 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: '&:hover', respectPrefix: true }] + let formats = [ + { format: ':merge(.group):hover &', respectPrefix: true }, + { format: '.dark &', respectPrefix: true }, + ] - expect(finalizeSelector(selector, formats, { candidate })).toEqual( - '.hover\\:placeholder-red-500:hover::placeholder' - ) - }) + expect(finalizeSelector(selector, formats, { candidate })).toEqual( + '.dark .group:hover .dark\\:group-hover\\:text-center' + ) + }) - it('example b', () => { - let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])' - let candidate = 'group-hover:hover:space-x-4' + it('should work for group-hover and class dark mode combinations (reversed)', () => { + let selector = '.text-center' + let candidate = 'group-hover:dark:text-center' - let formats = [ - { format: '&:hover', respectPrefix: true }, - { format: ':merge(.group):hover &', respectPrefix: true }, - ] + let formats = [{ format: '.dark &' }, { 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])' - ) - }) + expect(finalizeSelector(selector, formats, { candidate })).toEqual( + '.group:hover .dark .group-hover\\:dark\\:text-center' + ) + }) - 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', - }) - - expect(result).toEqual(after.replace('&', '.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')) }) }) diff --git a/tests/generate-rules.test.js b/tests/generate-rules.test.js index 3aa8592bc726..57740c12881d 100644 --- a/tests/generate-rules.test.js +++ b/tests/generate-rules.test.js @@ -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})`) - }, - ], - } - let context = createContext(resolveConfig(config)) - let rules = generateRules( - new Set([ - // Invalid, missing `-` - 'group[:hover]:underline', +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', + // Invalid, `-` should not be there + '@-[200px]:underline', - // Valid - 'group-[:hover]:underline', - '@[200px]:underline', - ]), - context - ) + // Valid + 'group-[:hover]:underline', + '@[200px]:underline', + ]), + context + ) - // Ensure we only have 2 valid rules - expect(rules).toHaveLength(2) + // 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 { + // Ensure we have the correct values + expect(rules[0][1].toString()).toMatchFormattedCss(css` + .group:hover .group-\[\:hover\]\:underline { + text-decoration-line: 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; - } - } - `) - }) + } + `) }) diff --git a/tests/getClassList.test.js b/tests/getClassList.test.js index 65ca7a074b26..94a790a78447 100644 --- a/tests/getClassList.test.js +++ b/tests/getClassList.test.js @@ -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') - }) - - 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 handle the DEFAULT case correctly - expect(classes).toContain('tw-border') + expect(classes).not.toContain('-m-DEFAULT') +}) - // 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') +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 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') + + // 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() + + expect(classes).not.toContain('-tw-m-DEFAULT') +}) - // 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') +it('should not generate utilities with opacity by default', () => { + let config = {} + let context = createContext(resolveConfig(config)) + let classes = context.getClassList() - config = { - prefix: 'tw-', - theme: { extend: { margin: { DEFAULT: '5px' } } }, - } - context = createContext(resolveConfig(config)) - classes = context.getClassList() + expect(classes).not.toContain('bg-red-500/50') +}) - expect(classes).not.toContain('-tw-m-DEFAULT') - }) +it('should not include metadata 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.every((cls) => typeof cls === 'string')).toEqual(true) - expect(classes).not.toContain('bg-red-500/50') - }) + expect(classes).toContain('bg-red-500') + expect(classes).toContain('text-2xl') +}) - it('should not include metadata by default', () => { - let config = {} - let context = createContext(resolveConfig(config)) - let classes = context.getClassList() +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).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', + ], + }, + ]) +}) - expect(classes.every((cls) => typeof cls === 'string')).toEqual(true) +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'] }]) +}) - expect(classes).toContain('bg-red-500') - expect(classes).toContain('text-2xl') - }) +it('should not generate utilities with opacity even if safe-listed', () => { + let config = { + safelist: [ + { + pattern: /^bg-red-(400|500)(\/(40|50))?$/, + }, + ], + } - it('should generate utilities with modifier data when requested', () => { - let config = {} - let context = createContext(resolveConfig(config)) - let classes = context.getClassList({ includeMetadata: true }) + let context = createContext(resolveConfig(config)) + let classes = context.getClassList() - expect(classes).not.toContain('bg-red-500') - expect(classes).not.toContain('text-2xl') + expect(classes).not.toContain('bg-red-500/50') +}) - 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, - } - ) - }), - ], - } - 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, - }, +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') }) diff --git a/tests/getSortOrder.test.js b/tests/getSortOrder.test.js index 2af2bcfc2efb..38ce8b565950 100644 --- a/tests/getSortOrder.test.js +++ b/tests/getSortOrder.test.js @@ -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 = {} + let context = createContext(resolveConfig(config)) + expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output) +}) - // 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 = {} +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'], - - // 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('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) - } - }) + 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) + } }) it('sorts based on first occurence of a candidate / rule', () => { diff --git a/tests/getVariants.test.js b/tests/getVariants.test.js index 8c4022186a98..51bab3dc5463 100644 --- a/tests/getVariants.test.js +++ b/tests/getVariants.test.js @@ -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)) - - 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() - }) - it('should provide selectors for simple variants', () => { - 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() - let variant = variants.find((v) => v.name === 'hover') - expect(variant.selectors()).toEqual(['&:hover']) + expect(variants).toContainEqual({ + name: 'hover', + isArbitrary: false, + hasDash: true, + values: [], + selectors: expect.any(Function), }) - it('should provide selectors for parallel variants', () => { - let config = {} - let context = createContext(resolveConfig(config)) + expect(variants).toContainEqual({ + name: 'group', + isArbitrary: true, + hasDash: true, + values: expect.any(Array), + selectors: expect.any(Function), + }) - let variants = context.getVariants() + // `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 variant = variants.find((v) => v.name === 'marker') - expect(variant.selectors()).toEqual(['& *::marker', '&::marker']) - }) +it('should provide selectors for simple variants', () => { + let config = {} + let context = createContext(resolveConfig(config)) - it('should provide selectors for complex matchVariant variants like `group`', () => { - let config = {} - let context = createContext(resolveConfig(config)) + let variants = context.getVariants() - let variants = context.getVariants() + let variant = variants.find((v) => v.name === 'hover') + expect(variant.selectors()).toEqual(['&:hover']) +}) - 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 &']) - }) +it('should provide selectors for parallel variants', () => { + let config = {} + let context = createContext(resolveConfig(config)) - it('should provide selectors for complex matchVariant variants like `group` (when using a prefix)', () => { - let config = { prefix: 'tw-' } - let context = createContext(resolveConfig(config)) - - let variants = context.getVariants() - - 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 === '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 === '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(['.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 &']) +}) + +it('should provide selectors for complex matchVariant variants like `group` (when using a prefix)', () => { + let config = { prefix: 'tw-' } + let context = createContext(resolveConfig(config)) + + let variants = context.getVariants() + + 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)) - - 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', - ]) - }) + `.${modifier}\\/${value} &:focus`, + ] + }) + }, + ], + } + let context = createContext(resolveConfig(config)) + + 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', + ]) +}) - 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) +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) }) - }, - ], - } - let context = createContext(resolveConfig(config)) - - let variants = context.getVariants() - let variant = variants.find((v) => v.name === 'foo') - expect(variant.selectors({})).toEqual(['@supports (display: grid) { .foo .foo\\:& }']) - }) + // 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 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)) - - 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)', - ]) +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 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)', + ]) }) diff --git a/tests/import-syntax.test.js b/tests/import-syntax.test.js index 992ef81d5f51..99f5163f7306 100644 --- a/tests/import-syntax.test.js +++ b/tests/import-syntax.test.js @@ -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` -

Hello world!

-
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - function ({ addBase }) { - addBase({ - h1: { - fontSize: '32px', - }, - }) - }, - ], - } +test('using @import instead of @tailwind', () => { + let config = { + content: [ + { + raw: html` +

Hello world!

+
+
+
+
+ `, + }, + ], + 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%; - } - @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; + max-width: 640px; } - ${defaults} + } + @media (min-width: 768px) { .container { - width: 100%; + max-width: 768px; } - @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 { - background-color: #000; + } + @media (min-width: 1024px) { + .container { + max-width: 1024px; } - @media (min-width: 768px) { - .md\:hover\:text-center:hover { - text-align: center; - } + } + @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; + } + } + `) }) }) diff --git a/tests/important-boolean.test.js b/tests/important-boolean.test.js index 4853f2a1f1db..116d97ec8ff6 100644 --- a/tests/important-boolean.test.js +++ b/tests/important-boolean.test.js @@ -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' - -crosscheck(() => { - test('important boolean', () => { - let config = { - important: true, - darkMode: 'selector', - content: [ - { - raw: html` -
-
-
-
-
-
-
-
-
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - function ({ addComponents, addUtilities }) { - addComponents( - { - '.btn': { - button: 'yes', - }, +import { run, html, css, defaults } from './util/run' + +test('important boolean', () => { + let config = { + important: true, + darkMode: 'selector', + content: [ + { + raw: html` +
+
+
+
+
+
+
+
+
+
+
+
+ `, + }, + ], + 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; + } } - - let input = css` - @tailwind base; - @tailwind components; - @layer components { - .custom-component { - @apply font-bold; - } - .custom-important-component { - @apply text-center !important; - } + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults} + .container { + width: 100%; } - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults} + @media (min-width: 640px) { .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; - } + max-width: 640px; } - .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; + } + @media (min-width: 768px) { + .container { + max-width: 768px; } - .custom-util { - button: no; + } + @media (min-width: 1024px) { + .container { + max-width: 1024px; } - .group:hover .group-hover\:focus-within\:text-left:focus-within { - text-align: left !important; + } + @media (min-width: 1280px) { + .container { + max-width: 1280px; } - @media (prefers-reduced-motion: no-preference) { - .motion-safe\:hover\:text-center:hover { - text-align: center !important; - } + } + @media (min-width: 1536px) { + .container { + max-width: 1536px; } - @media (min-width: 768px) { - .md\:hover\:text-right:hover { - text-align: right !important; - } + } + .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); } - .rtl\:active\:text-center:active:where([dir='rtl'], [dir='rtl'] *) { + } + .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; } - .dark\:focus\:text-left:focus:where(.dark, .dark *) { - text-align: left !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)) +// This is in a describe block so we can use `afterEach` :) +describe('duplicate elision', () => { + let filePath = path.resolve(__dirname, './important-boolean-duplicates.test.html') - test('important rules are not duplicated when rebuilding', async () => { - let config = { - important: true, - content: [filePath], - } + afterEach(async () => await fs.promises.unlink(filePath)) - await fs.promises.writeFile( - config.content[0], - html` -
-
- ` - ) + test('important rules are not duplicated when rebuilding', async () => { + let config = { + important: true, + content: [filePath], + } - let input = css` - @tailwind utilities; + await fs.promises.writeFile( + config.content[0], + html` +
+
` + ) - let result = await run(input, config) - let allContexts = Array.from(sharedState.contextMap.values()) + let input = css` + @tailwind utilities; + ` - let context = allContexts[allContexts.length - 1] + let result = await run(input, config) + let allContexts = Array.from(sharedState.contextMap.values()) - let ruleCacheSize1 = context.ruleCache.size + let context = allContexts[allContexts.length - 1] - expect(result.css).toMatchFormattedCss(css` - .ml-2 { - margin-left: 0.5rem !important; - } - .ml-4 { - margin-left: 1rem !important; - } - `) + let ruleCacheSize1 = context.ruleCache.size - await fs.promises.writeFile( - config.content[0], - html` -
-
- ` - ) + expect(result.css).toMatchFormattedCss(css` + .ml-2 { + margin-left: 0.5rem !important; + } + .ml-4 { + margin-left: 1rem !important; + } + `) - result = await run(input, config) + await fs.promises.writeFile( + config.content[0], + html` +
+
+ ` + ) - let ruleCacheSize2 = context.ruleCache.size + result = await run(input, config) - 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; - } - `) + 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) }) }) diff --git a/tests/important-modifier-prefix.test.js b/tests/important-modifier-prefix.test.js index 1e9f2bd22d68..3c34d4f2a99c 100644 --- a/tests/important-modifier-prefix.test.js +++ b/tests/important-modifier-prefix.test.js @@ -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` -
-
-
-
-
-
`, - }, - ], - corePlugins: { preflight: false }, - } +test('important modifier with prefix', () => { + let config = { + important: false, + prefix: 'tw-', + darkMode: 'selector', + content: [ + { + raw: 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} + 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: 1024px) { - .\!tw-container { - max-width: 1024px !important; - } - } - @media (min-width: 1280px) { - .\!tw-container { - max-width: 1280px !important; - } + } + @media (min-width: 768px) { + .\!tw-container { + max-width: 768px !important; } - @media (min-width: 1536px) { - .\!tw-container { - max-width: 1536px !important; - } + } + @media (min-width: 1024px) { + .\!tw-container { + max-width: 1024px !important; } - .\!tw-font-bold { - font-weight: 700 !important; + } + @media (min-width: 1280px) { + .\!tw-container { + max-width: 1280px !important; } - .hover\:\!tw-text-center:hover { - text-align: center !important; + } + @media (min-width: 1536px) { + .\!tw-container { + max-width: 1536px !important; } - @media (min-width: 1024px) { - .lg\:\!tw-opacity-50 { - opacity: 0.5 !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; } - @media (min-width: 1280px) { - .xl\:focus\:disabled\:\!tw-float-right:disabled:focus { - float: right !important; - } + } + @media (min-width: 1280px) { + .xl\:focus\:disabled\:\!tw-float-right:disabled:focus { + float: right !important; } - `) - }) + } + `) }) }) diff --git a/tests/important-modifier.test.js b/tests/important-modifier.test.js index 0cfa659d8d7e..0060be849139 100644 --- a/tests/important-modifier.test.js +++ b/tests/important-modifier.test.js @@ -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` -
-
-
-
-
-
-
- `, - }, - ], - 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` +
+
+
+
+
+
+
+ `, + }, + ], + 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: 1024px) { - .\!container { - max-width: 1024px !important; - } - } - @media (min-width: 1280px) { - .\!container { - max-width: 1280px !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: 768px) { + .\!container { + max-width: 768px !important; } - @media (min-width: 1024px) { - .lg\:\!opacity-50 { - opacity: 0.5 !important; - } + } + @media (min-width: 1024px) { + .\!container { + max-width: 1024px !important; } - @media (min-width: 1280px) { - .xl\:focus\:disabled\:\!float-right:disabled:focus { - float: right !important; - } + } + @media (min-width: 1280px) { + .\!container { + max-width: 1280px !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; + } + } + @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`
`, - }, - ], - 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`
`, + }, + ], + 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`
`, - }, - ], - corePlugins: { preflight: false }, - } +test('the important modifier does not break keyframes', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + 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; + } + `) }) }) diff --git a/tests/important-selector.test.js b/tests/important-selector.test.js index 840c572864c6..cd3fca1f9fcb 100644 --- a/tests/important-selector.test.js +++ b/tests/important-selector.test.js @@ -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` -
-
-
-
-
-
-
-
-
-
-
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - function ({ addComponents, addUtilities }) { - addComponents( - { - '.btn': { - button: 'yes', - }, +test('important selector', () => { + let config = { + important: '#app', + darkMode: 'selector', + content: [ + { + raw: html` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `, + }, + ], + 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; - } + let input = css` + @tailwind base; + @tailwind components; + @layer components { + .custom-component { + @apply font-bold; } - @tailwind utilities; - ` + .custom-important-component { + @apply text-center !important; + } + } + @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} + .container { + width: 100%; + } + @media (min-width: 640px) { .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; - } - } - #app .btn { - button: yes; + max-width: 640px; } - @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; + } + @media (min-width: 768px) { + .container { + max-width: 768px; } - .custom-util { - button: no; + } + @media (min-width: 1024px) { + .container { + max-width: 1024px; } - #app :is(.group:hover .group-hover\:focus-within\:text-left:focus-within) { - text-align: left; + } + @media (min-width: 1280px) { + .container { + max-width: 1280px; } - @media (prefers-reduced-motion: no-preference) { - #app :is(.motion-safe\:hover\:text-center:hover) { - text-align: center; - } + } + @media (min-width: 1536px) { + .container { + max-width: 1536px; } - @media (min-width: 768px) { - #app :is(.md\:hover\:text-right:hover) { - text-align: right; - } + } + #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(.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; - } - #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; + } + @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'] *)) { + 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`
`, - }, - ], - corePlugins: { preflight: false }, - } +test('pseudo-elements are appended after the `:is()`', () => { + let config = { + important: '#app', + darkMode: 'selector', + content: [ + { + raw: 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) => { - 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)); + } + `) }) }) diff --git a/tests/kitchen-sink.test.js b/tests/kitchen-sink.test.js index 27dedb46a8ef..cd23e2b2fbae 100644 --- a/tests/kitchen-sink.test.js +++ b/tests/kitchen-sink.test.js @@ -1,1383 +1,817 @@ -import { crosscheck, run, html, css, defaults } from './util/run' +import { run, html, css, defaults } from './util/run' -crosscheck(({ stable, oxide }) => { - test('it works', () => { - let config = { - darkMode: 'selector', - content: [ - { - raw: html` -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+test('it works', () => { + let config = { + darkMode: 'selector', + content: [ + { + raw: html` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- - `, + things: Array /* PropType */, + }, + setup: () => { + const count = ref(0) + // Weird regex-looking stuff that once caused a stack overflow in candidatePermutations + const pattern = ' ]-[] ' + return { + count, + stuff: [] /* string[] | undefined */, + } + }, + }) + + `, + }, + ], + corePlugins: { preflight: false }, + theme: { + extend: { + screens: { + range: { min: '1280px', max: '1535px' }, + multi: [{ min: '640px', max: '767px' }, { max: '868px' }], }, - ], - corePlugins: { preflight: false }, - theme: { - extend: { - screens: { - range: { min: '1280px', max: '1535px' }, - multi: [{ min: '640px', max: '767px' }, { max: '868px' }], - }, - gradientColorStops: { - foo: '#bada55', - }, - backgroundImage: { - 'hero--home-1': "url('/images/homepage-1.jpg')", - }, + gradientColorStops: { + foo: '#bada55', + }, + backgroundImage: { + 'hero--home-1': "url('/images/homepage-1.jpg')", }, }, - plugins: [ - function ({ addVariant }) { - addVariant( - 'foo', - ({ container }) => { - container.walkRules((rule) => { - rule.selector = `.foo\\:${rule.selector.slice(1)}` - rule.walkDecls((decl) => { - decl.important = true - }) + }, + plugins: [ + function ({ addVariant }) { + addVariant( + 'foo', + ({ container }) => { + container.walkRules((rule) => { + rule.selector = `.foo\\:${rule.selector.slice(1)}` + rule.walkDecls((decl) => { + decl.important = true }) + }) + }, + { before: 'sm' } + ) + }, + function ({ addUtilities, addBase, theme }) { + addBase({ + h1: { + fontSize: theme('fontSize.2xl'), + fontWeight: theme('fontWeight.bold'), + '&:first-child': { + marginTop: '0px', }, - { before: 'sm' } - ) - }, - function ({ addUtilities, addBase, theme }) { - addBase({ - h1: { - fontSize: theme('fontSize.2xl'), - fontWeight: theme('fontWeight.bold'), - '&:first-child': { - marginTop: '0px', - }, + }, + }) + addUtilities( + { + '.magic-none': { + magic: 'none', }, - }) - addUtilities( - { - '.magic-none': { - magic: 'none', - }, - '.magic-tons': { - magic: 'tons', - }, + '.magic-tons': { + magic: 'tons', }, - ['responsive', 'hover'] - ) - }, - ], - } + }, + ['responsive', 'hover'] + ) + }, + ], + } - let input = css` - @layer utilities { - .custom-util { - background: #abcdef; - } - *, - ::before, - ::after, - ::backdrop { - margin: 10px; - } + let input = css` + @layer utilities { + .custom-util { + background: #abcdef; } - @layer components { - .test-apply-font-variant { - @apply ordinal tabular-nums; - } - .custom-component { - background: #123456; - } - *, - ::before, - ::after, - ::backdrop { - padding: 5px; - } - .foo .bg-black { - appearance: none; - } + *, + ::before, + ::after, + ::backdrop { + margin: 10px; } - @layer base { - div { - background: #654321; - } + } + @layer components { + .test-apply-font-variant { + @apply ordinal tabular-nums; + } + .custom-component { + background: #123456; + } + *, + ::before, + ::after, + ::backdrop { + padding: 5px; + } + .foo .bg-black { + appearance: none; } + } + @layer base { + div { + background: #654321; + } + } + .theme-test { + font-family: theme('fontFamily.sans'); + color: theme('colors.blue.500'); + } + @screen lg { + .screen-test { + color: purple; + } + } + .apply-1 { + @apply mt-6; + } + .apply-2 { + @apply mt-6; + } + .apply-test { + @apply mt-6 bg-pink-500 hover:font-bold focus:hover:font-bold sm:bg-green-500 sm:focus:even:bg-pink-200; + } + .apply-components { + @apply container mx-auto; + } + .drop-empty-rules { + @apply hover:font-bold; + } + .apply-group { + @apply group-hover:font-bold; + } + .apply-dark-mode { + @apply dark:font-bold; + } + .apply-with-existing:hover { + @apply font-normal sm:bg-green-500; + } + .multiple, + .selectors { + @apply font-bold group-hover:font-normal; + } + .list { + @apply space-y-4; + } + .nested { + .example { + @apply font-bold hover:font-normal; + } + } + .apply-order-a { + @apply m-5 mt-6; + } + .apply-order-b { + @apply m-5 mt-6; + } + .apply-dark-group-example-a { + @apply dark:group-hover:bg-green-500; + } + .crazy-example { + @apply sm:motion-safe:group-active:focus:opacity-10; + } + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` .theme-test { - font-family: theme('fontFamily.sans'); - color: theme('colors.blue.500'); + color: #3b82f6; + font-family: ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, + Segoe UI Symbol, Noto Color Emoji; } - @screen lg { + @media (min-width: 1024px) { .screen-test { color: purple; } } - .apply-1 { - @apply mt-6; - } + .apply-1, .apply-2 { - @apply mt-6; + margin-top: 1.5rem; } .apply-test { - @apply mt-6 bg-pink-500 hover:font-bold focus:hover:font-bold sm:bg-green-500 sm:focus:even:bg-pink-200; + --tw-bg-opacity: 1; + background-color: rgb(236 72 153 / var(--tw-bg-opacity)); + margin-top: 1.5rem; + } + .apply-test:hover, + .apply-test:hover:focus { + font-weight: 700; + } + @media (min-width: 640px) { + .apply-test { + --tw-bg-opacity: 1; + background-color: rgb(34 197 94 / var(--tw-bg-opacity)); + } + .apply-test:nth-child(2n):focus { + --tw-bg-opacity: 1; + background-color: rgb(251 207 232 / var(--tw-bg-opacity)); + } } .apply-components { - @apply container mx-auto; + width: 100%; } - .drop-empty-rules { - @apply hover:font-bold; + @media (min-width: 640px) { + .apply-components { + max-width: 640px; + } + } + @media (min-width: 768px) { + .apply-components { + max-width: 768px; + } + } + @media (min-width: 1024px) { + .apply-components { + max-width: 1024px; + } + } + @media (min-width: 1280px) { + .apply-components { + max-width: 1280px; + } + } + @media (min-width: 1536px) { + .apply-components { + max-width: 1536px; + } + } + .apply-components { + margin-left: auto; + margin-right: auto; } - .apply-group { - @apply group-hover:font-bold; + .drop-empty-rules:hover, + .group:hover .apply-group { + font-weight: 700; } - .apply-dark-mode { - @apply dark:font-bold; + .apply-dark-mode:where(.dark, .dark *) { + font-weight: 700; } .apply-with-existing:hover { - @apply font-normal sm:bg-green-500; + font-weight: 400; + } + @media (min-width: 640px) { + .apply-with-existing:hover { + --tw-bg-opacity: 1; + background-color: rgb(34 197 94 / var(--tw-bg-opacity)); + } } .multiple, .selectors { - @apply font-bold group-hover:font-normal; + font-weight: 700; } - .list { - @apply space-y-4; + .group:hover .multiple, + .group:hover .selectors { + font-weight: 400; } - .nested { - .example { - @apply font-bold hover:font-normal; - } + .list > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1rem * var(--tw-space-y-reverse)); } - .apply-order-a { - @apply m-5 mt-6; + .nested .example { + font-weight: 700; } + .nested .example:hover { + font-weight: 400; + } + .apply-order-a, .apply-order-b { - @apply m-5 mt-6; + margin: 1.5rem 1.25rem 1.25rem; + } + .group:hover .apply-dark-group-example-a:where(.dark, .dark *) { + --tw-bg-opacity: 1; + background-color: rgb(34 197 94 / var(--tw-bg-opacity)); } - .apply-dark-group-example-a { - @apply dark:group-hover:bg-green-500; + @media (min-width: 640px) { + @media (prefers-reduced-motion: no-preference) { + .group:active .crazy-example:focus { + opacity: 0.1; + } + } } - .crazy-example { - @apply sm:motion-safe:group-active:focus:opacity-10; + h1 { + font-size: 1.5rem; + font-weight: 700; } - @tailwind base; - @tailwind components; - @tailwind utilities; - ` - - return run(input, config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .theme-test { - color: #3b82f6; - font-family: ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, - Segoe UI Symbol, Noto Color Emoji; + h1:first-child { + margin-top: 0; + } + div { + background: #654321; + } + ${defaults} + .container { + width: 100%; + } + @media (min-width: 640px) { + .container { + max-width: 640px; } - @media (min-width: 1024px) { - .screen-test { - color: purple; - } + } + @media (min-width: 768px) { + .container { + max-width: 768px; } - .apply-1, - .apply-2 { - margin-top: 1.5rem; + } + @media (min-width: 1024px) { + .container { + max-width: 1024px; } - .apply-test { - --tw-bg-opacity: 1; - background-color: rgb(236 72 153 / var(--tw-bg-opacity)); - margin-top: 1.5rem; + } + @media (min-width: 1280px) { + .container { + max-width: 1280px; } - .apply-test:hover, - .apply-test:hover:focus { - font-weight: 700; + } + @media (min-width: 1536px) { + .container { + max-width: 1536px; } - @media (min-width: 640px) { - .apply-test { - --tw-bg-opacity: 1; - background-color: rgb(34 197 94 / var(--tw-bg-opacity)); - } - .apply-test:nth-child(2n):focus { - --tw-bg-opacity: 1; - background-color: rgb(251 207 232 / var(--tw-bg-opacity)); - } + } + .test-apply-font-variant { + --tw-ordinal: ordinal; + --tw-numeric-spacing: tabular-nums; + font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) + var(--tw-numeric-spacing) var(--tw-numeric-fraction); + } + .custom-component { + background: #123456; + } + *, + :before, + :after, + ::backdrop { + padding: 5px; + } + .foo .bg-black { + appearance: none; + } + .inset-6 { + inset: 1.5rem; + } + .inset-x-1 { + left: 0.25rem; + right: 0.25rem; + } + .end-8 { + inset-inline-end: 2rem; + } + .start-4 { + inset-inline-start: 1rem; + } + .mx-1 { + margin-left: 0.25rem; + margin-right: 0.25rem; + } + .me-8 { + margin-inline-end: 2rem; + } + .ms-4 { + margin-inline-start: 1rem; + } + .mt-6 { + margin-top: 1.5rem; + } + .line-clamp-2 { + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + display: -webkit-box; + overflow: hidden; + } + .line-clamp-\[33\] { + -webkit-line-clamp: 33; + -webkit-box-orient: vertical; + display: -webkit-box; + overflow: hidden; + } + .line-clamp-\[var\(--line-clamp-variable\)\] { + -webkit-line-clamp: var(--line-clamp-variable); + -webkit-box-orient: vertical; + display: -webkit-box; + overflow: hidden; + } + .line-clamp-none { + -webkit-line-clamp: none; + -webkit-box-orient: horizontal; + display: block; + overflow: visible; + } + .scale-50 { + --tw-scale-x: 0.5; + --tw-scale-y: 0.5; + 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)); + } + .transform { + 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)); + } + .grid-cols-\[200px\,repeat\(auto-fill\,minmax\(15\%\,100px\)\)\,300px\] { + grid-template-columns: 200px repeat(auto-fill, minmax(15%, 100px)) 300px; + } + .rounded-e { + border-start-end-radius: 0.25rem; + border-end-end-radius: 0.25rem; + } + .rounded-s { + border-start-start-radius: 0.25rem; + border-end-start-radius: 0.25rem; + } + .rounded-es { + border-end-start-radius: 0.25rem; + } + .rounded-ss { + border-start-start-radius: 0.25rem; + } + .border-2 { + border-width: 2px; + } + .border-e-4 { + border-inline-end-width: 4px; + } + .border-s-0 { + border-inline-start-width: 0; + } + .border-black { + --tw-border-opacity: 1; + border-color: rgb(0 0 0 / var(--tw-border-opacity)); + } + .border-e-red-400 { + --tw-border-opacity: 1; + border-inline-end-color: rgb(248 113 113 / var(--tw-border-opacity)); + } + .border-s-green-500 { + --tw-border-opacity: 1; + border-inline-start-color: rgb(34 197 94 / var(--tw-border-opacity)); + } + .bg-black { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); + } + .bg-green-500 { + --tw-bg-opacity: 1; + background-color: rgb(34 197 94 / var(--tw-bg-opacity)); + } + .bg-opacity-50 { + --tw-bg-opacity: 0.5; + } + .bg-gradient-to-r { + background-image: linear-gradient(to right, var(--tw-gradient-stops)); + } + .bg-hero--home-1 { + background-image: url('/images/homepage-1.jpg'); + } + .from-foo { + --tw-gradient-from: #bada55 var(--tw-gradient-from-position); + --tw-gradient-to: #bada5500 var(--tw-gradient-to-position); + --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); + } + .px-1 { + padding-left: 0.25rem; + padding-right: 0.25rem; + } + .pe-8 { + padding-inline-end: 2rem; + } + .ps-4 { + padding-inline-start: 1rem; + } + .pt-6 { + padding-top: 1.5rem; + } + .text-center { + text-align: center; + } + .font-medium { + font-weight: 500; + } + .shadow-md { + --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), + 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); + } + .shadow-sm { + --tw-shadow: 0 1px 2px 0 #0000000d; + --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); + } + .magic-none { + magic: none; + } + .magic-tons { + magic: tons; + } + .custom-util { + background: #abcdef; + } + *, + :before, + :after, + ::backdrop { + margin: 10px; + } + .hover\:container:hover { + width: 100%; + } + @media (min-width: 640px) { + .hover\:container:hover { + max-width: 640px; } - .apply-components { + } + @media (min-width: 768px) { + .hover\:container:hover { + max-width: 768px; + } + } + @media (min-width: 1024px) { + .hover\:container:hover { + max-width: 1024px; + } + } + @media (min-width: 1280px) { + .hover\:container:hover { + max-width: 1280px; + } + } + @media (min-width: 1536px) { + .hover\:container:hover { + max-width: 1536px; + } + } + @media (min-width: 640px) { + .sm\:container { width: 100%; } @media (min-width: 640px) { - .apply-components { + .sm\:container { max-width: 640px; } } @media (min-width: 768px) { - .apply-components { + .sm\:container { max-width: 768px; } } @media (min-width: 1024px) { - .apply-components { + .sm\:container { max-width: 1024px; } } @media (min-width: 1280px) { - .apply-components { + .sm\:container { max-width: 1280px; } } @media (min-width: 1536px) { - .apply-components { + .sm\:container { max-width: 1536px; } } - .apply-components { - margin-left: auto; - margin-right: auto; - } - .drop-empty-rules:hover, - .group:hover .apply-group { - font-weight: 700; - } - .apply-dark-mode:where(.dark, .dark *) { - font-weight: 700; - } - .apply-with-existing:hover { - font-weight: 400; - } - @media (min-width: 640px) { - .apply-with-existing:hover { - --tw-bg-opacity: 1; - background-color: rgb(34 197 94 / var(--tw-bg-opacity)); - } - } - .multiple, - .selectors { - font-weight: 700; - } - .group:hover .multiple, - .group:hover .selectors { - font-weight: 400; - } - .list > :not([hidden]) ~ :not([hidden]) { - --tw-space-y-reverse: 0; - margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); - margin-bottom: calc(1rem * var(--tw-space-y-reverse)); - } - .nested .example { - font-weight: 700; - } - .nested .example:hover { - font-weight: 400; - } - .apply-order-a, - .apply-order-b { - margin: 1.5rem 1.25rem 1.25rem; - } - .group:hover .apply-dark-group-example-a:where(.dark, .dark *) { - --tw-bg-opacity: 1; - background-color: rgb(34 197 94 / var(--tw-bg-opacity)); - } - @media (min-width: 640px) { - @media (prefers-reduced-motion: no-preference) { - .group:active .crazy-example:focus { - opacity: 0.1; - } - } - } - h1 { - font-size: 1.5rem; - font-weight: 700; - } - h1:first-child { - margin-top: 0; - } - div { - background: #654321; - } - ${defaults} - .container { + } + @media (min-width: 768px) { + .md\:container { width: 100%; } @media (min-width: 640px) { - .container { + .md\:container { max-width: 640px; } } @media (min-width: 768px) { - .container { + .md\:container { max-width: 768px; } } @media (min-width: 1024px) { - .container { + .md\:container { max-width: 1024px; } } @media (min-width: 1280px) { - .container { + .md\:container { max-width: 1280px; } } @media (min-width: 1536px) { - .container { + .md\:container { max-width: 1536px; } } - .test-apply-font-variant { - --tw-ordinal: ordinal; - --tw-numeric-spacing: tabular-nums; - font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) - var(--tw-numeric-spacing) var(--tw-numeric-fraction); + } + .first\:pt-0:first-child { + padding-top: 0; + } + .hover\:scale-75:hover { + --tw-scale-x: 0.75; + --tw-scale-y: 0.75; + 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)); + } + .hover\:font-bold:hover { + font-weight: 700; + } + .hover\:shadow-lg:hover { + --tw-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a; + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), + 0 4px 6px -4px 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\:custom-util:hover { + background: #abcdef; + } + .focus\:font-normal:focus { + font-weight: 400; + } + .focus\:ring-2:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) + var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) + var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); + } + .focus\:ring-blue-500:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity)); + } + .focus\:hover\:font-light:hover:focus { + font-weight: 300; + } + .disabled\:font-bold:disabled { + font-weight: 700; + } + .group:hover .group-hover\:opacity-100 { + opacity: 1; + } + .group:hover .group-hover\:custom-util { + background: #abcdef; + } + .group:active .group-active\:opacity-10 { + opacity: 0.1; + } + .foo\:custom-util, + .foo\:hover\:custom-util:hover { + background: #abcdef !important; + } + @media (prefers-reduced-motion: no-preference) { + .motion-safe\:transition { + transition-property: color, background-color, border-color, text-decoration-color, fill, + stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-duration: 0.15s; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + } + .motion-safe\:custom-util { + background: #abcdef; } - .custom-component { - background: #123456; + } + @media (prefers-reduced-motion: reduce) { + .motion-reduce\:transition { + transition-property: color, background-color, border-color, text-decoration-color, fill, + stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-duration: 0.15s; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } - *, - :before, - :after, - ::backdrop { - padding: 5px; + } + @media (min-width: 640px) { + .sm\:text-center { + text-align: center; } - .foo .bg-black { - appearance: none; + .sm\:tabular-nums { + --tw-numeric-spacing: tabular-nums; + font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) + var(--tw-numeric-spacing) var(--tw-numeric-fraction); } - .inset-6 { - inset: 1.5rem; + .sm\:custom-util { + background: #abcdef; } - .inset-x-1 { - left: 0.25rem; - right: 0.25rem; + @media (prefers-reduced-motion: no-preference) { + .group:active .sm\:motion-safe\:group-active\:focus\:opacity-10:focus { + opacity: 0.1; + } } - .end-8 { - inset-inline-end: 2rem; + } + @media (min-width: 768px) { + .md\:text-center { + text-align: center; } - .start-4 { - inset-inline-start: 1rem; + .md\:opacity-50 { + opacity: 0.5; } - .mx-1 { - margin-left: 0.25rem; - margin-right: 0.25rem; + .md\:shadow-sm { + --tw-shadow: 0 1px 2px 0 #0000000d; + --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); } - .me-8 { - margin-inline-end: 2rem; + .md\:hover\:border-r-blue-500\/30:hover { + border-right-color: #3b82f64d; } - .ms-4 { - margin-inline-start: 1rem; + .md\:hover\:opacity-20:hover { + opacity: 0.2; } - .mt-6 { - margin-top: 1.5rem; + @media (prefers-reduced-motion: no-preference) { + .md\:motion-safe\:hover\:transition:hover { + transition-property: color, background-color, border-color, text-decoration-color, fill, + stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-duration: 0.15s; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + } } - .line-clamp-2 { - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - display: -webkit-box; - overflow: hidden; + @media (min-width: 640px) { + .md\:sm\:text-center { + text-align: center; + } } - .line-clamp-\[33\] { - -webkit-line-clamp: 33; - -webkit-box-orient: vertical; - display: -webkit-box; - overflow: hidden; + } + @media (min-width: 1280px) and (max-width: 1535px) { + .range\:text-right { + text-align: right; } - .line-clamp-\[var\(--line-clamp-variable\)\] { - -webkit-line-clamp: var(--line-clamp-variable); - -webkit-box-orient: vertical; - display: -webkit-box; - overflow: hidden; - } - .line-clamp-none { - -webkit-line-clamp: none; - -webkit-box-orient: horizontal; - display: block; - overflow: visible; - } - .scale-50 { - --tw-scale-x: 0.5; - --tw-scale-y: 0.5; - 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)); - } - .transform { - 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)); - } - .grid-cols-\[200px\,repeat\(auto-fill\,minmax\(15\%\,100px\)\)\,300px\] { - grid-template-columns: 200px repeat(auto-fill, minmax(15%, 100px)) 300px; - } - .rounded-e { - border-start-end-radius: 0.25rem; - border-end-end-radius: 0.25rem; - } - .rounded-s { - border-start-start-radius: 0.25rem; - border-end-start-radius: 0.25rem; - } - .rounded-es { - border-end-start-radius: 0.25rem; - } - .rounded-ss { - border-start-start-radius: 0.25rem; - } - .border-2 { - border-width: 2px; - } - .border-e-4 { - border-inline-end-width: 4px; - } - .border-s-0 { - border-inline-start-width: 0; - } - .border-black { - --tw-border-opacity: 1; - border-color: rgb(0 0 0 / var(--tw-border-opacity)); - } - .border-e-red-400 { - --tw-border-opacity: 1; - border-inline-end-color: rgb(248 113 113 / var(--tw-border-opacity)); - } - .border-s-green-500 { - --tw-border-opacity: 1; - border-inline-start-color: rgb(34 197 94 / var(--tw-border-opacity)); - } - .bg-black { - --tw-bg-opacity: 1; - background-color: rgb(0 0 0 / var(--tw-bg-opacity)); - } - .bg-green-500 { - --tw-bg-opacity: 1; - background-color: rgb(34 197 94 / var(--tw-bg-opacity)); - } - .bg-opacity-50 { - --tw-bg-opacity: 0.5; - } - .bg-gradient-to-r { - background-image: linear-gradient(to right, var(--tw-gradient-stops)); - } - .bg-hero--home-1 { - background-image: url('/images/homepage-1.jpg'); - } - .from-foo { - --tw-gradient-from: #bada55 var(--tw-gradient-from-position); - --tw-gradient-to: #bada5500 var(--tw-gradient-to-position); - --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); - } - .px-1 { - padding-left: 0.25rem; - padding-right: 0.25rem; - } - .pe-8 { - padding-inline-end: 2rem; - } - .ps-4 { - padding-inline-start: 1rem; - } - .pt-6 { - padding-top: 1.5rem; - } - .text-center { - text-align: center; - } - .font-medium { - font-weight: 500; - } - .shadow-md { - --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; - --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), - 0 2px 4px -2px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), - var(--tw-shadow); - } - .shadow-sm { - --tw-shadow: 0 1px 2px 0 #0000000d; - --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), - var(--tw-shadow); - } - .magic-none { - magic: none; - } - .magic-tons { - magic: tons; - } - .custom-util { - background: #abcdef; - } - *, - :before, - :after, - ::backdrop { - margin: 10px; - } - .hover\:container:hover { - width: 100%; - } - @media (min-width: 640px) { - .hover\:container:hover { - max-width: 640px; - } - } - @media (min-width: 768px) { - .hover\:container:hover { - max-width: 768px; - } - } - @media (min-width: 1024px) { - .hover\:container:hover { - max-width: 1024px; - } - } - @media (min-width: 1280px) { - .hover\:container:hover { - max-width: 1280px; - } - } - @media (min-width: 1536px) { - .hover\:container:hover { - max-width: 1536px; - } - } - @media (min-width: 640px) { - .sm\:container { - width: 100%; - } - @media (min-width: 640px) { - .sm\:container { - max-width: 640px; - } - } - @media (min-width: 768px) { - .sm\:container { - max-width: 768px; - } - } - @media (min-width: 1024px) { - .sm\:container { - max-width: 1024px; - } - } - @media (min-width: 1280px) { - .sm\:container { - max-width: 1280px; - } - } - @media (min-width: 1536px) { - .sm\:container { - max-width: 1536px; - } - } - } - @media (min-width: 768px) { - .md\:container { - width: 100%; - } - @media (min-width: 640px) { - .md\:container { - max-width: 640px; - } - } - @media (min-width: 768px) { - .md\:container { - max-width: 768px; - } - } - @media (min-width: 1024px) { - .md\:container { - max-width: 1024px; - } - } - @media (min-width: 1280px) { - .md\:container { - max-width: 1280px; - } - } - @media (min-width: 1536px) { - .md\:container { - max-width: 1536px; - } - } - } - .first\:pt-0:first-child { - padding-top: 0; - } - .hover\:scale-75:hover { - --tw-scale-x: 0.75; - --tw-scale-y: 0.75; - 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)); - } - .hover\:font-bold:hover { - font-weight: 700; - } - .hover\:shadow-lg:hover { - --tw-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a; - --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), - 0 4px 6px -4px 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\:custom-util:hover { - background: #abcdef; - } - .focus\:font-normal:focus { - font-weight: 400; - } - .focus\:ring-2:focus { - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) - var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) - var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), - var(--tw-shadow, 0 0 #0000); - } - .focus\:ring-blue-500:focus { - --tw-ring-opacity: 1; - --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity)); - } - .focus\:hover\:font-light:hover:focus { - font-weight: 300; - } - .disabled\:font-bold:disabled { - font-weight: 700; - } - .group:hover .group-hover\:opacity-100 { - opacity: 1; - } - .group:hover .group-hover\:custom-util { - background: #abcdef; - } - .group:active .group-active\:opacity-10 { - opacity: 0.1; - } - .foo\:custom-util, - .foo\:hover\:custom-util:hover { - background: #abcdef !important; - } - @media (prefers-reduced-motion: no-preference) { - .motion-safe\:transition { - transition-property: color, background-color, border-color, text-decoration-color, fill, - stroke, opacity, box-shadow, transform, filter, backdrop-filter; - transition-duration: 0.15s; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - } - .motion-safe\:custom-util { - background: #abcdef; - } - } - @media (prefers-reduced-motion: reduce) { - .motion-reduce\:transition { - transition-property: color, background-color, border-color, text-decoration-color, fill, - stroke, opacity, box-shadow, transform, filter, backdrop-filter; - transition-duration: 0.15s; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - } - } - @media (min-width: 640px) { - .sm\:text-center { - text-align: center; - } - .sm\:tabular-nums { - --tw-numeric-spacing: tabular-nums; - font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) - var(--tw-numeric-spacing) var(--tw-numeric-fraction); - } - .sm\:custom-util { - background: #abcdef; - } - @media (prefers-reduced-motion: no-preference) { - .group:active .sm\:motion-safe\:group-active\:focus\:opacity-10:focus { - opacity: 0.1; - } - } - } - @media (min-width: 768px) { - .md\:text-center { - text-align: center; - } - .md\:opacity-50 { - opacity: 0.5; - } - .md\:shadow-sm { - --tw-shadow: 0 1px 2px 0 #0000000d; - --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), - var(--tw-shadow); - } - .md\:hover\:border-r-blue-500\/30:hover { - border-right-color: #3b82f64d; - } - .md\:hover\:opacity-20:hover { - opacity: 0.2; - } - @media (prefers-reduced-motion: no-preference) { - .md\:motion-safe\:hover\:transition:hover { - transition-property: color, background-color, border-color, text-decoration-color, - fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; - transition-duration: 0.15s; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - } - } - @media (min-width: 640px) { - .md\:sm\:text-center { - text-align: center; - } - } - } - @media (min-width: 1280px) and (max-width: 1535px) { - .range\:text-right { - text-align: right; - } - } - @media (min-width: 640px) and (max-width: 767px), (max-width: 868px) { - .multi\:text-left { - text-align: left; - } - } - .dark\:custom-util:where(.dark, .dark *) { - background: #abcdef; - } - @media (min-width: 768px) { - @media (prefers-reduced-motion: no-preference) { - .md\:dark\:motion-safe\:foo\:active\:custom-util:active:where(.dark, .dark *) { - background: #abcdef !important; - } - } - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .theme-test { - color: #3b82f6; - font-family: ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, - Segoe UI Symbol, Noto Color Emoji; - } - @media (min-width: 1024px) { - .screen-test { - color: purple; - } - } - .apply-1, - .apply-2 { - margin-top: 1.5rem; - } - .apply-test { - background-color: #ec4899; - margin-top: 1.5rem; - } - .apply-test:hover, - .apply-test:hover:focus { - font-weight: 700; - } - @media (min-width: 640px) { - .apply-test { - background-color: #22c55e; - } - .apply-test:nth-child(2n):focus { - background-color: #fbcfe8; - } - } - .apply-components { - width: 100%; - } - @media (min-width: 640px) { - .apply-components { - max-width: 640px; - } - } - @media (min-width: 768px) { - .apply-components { - max-width: 768px; - } - } - @media (min-width: 1024px) { - .apply-components { - max-width: 1024px; - } - } - @media (min-width: 1280px) { - .apply-components { - max-width: 1280px; - } - } - @media (min-width: 1536px) { - .apply-components { - max-width: 1536px; - } - } - .apply-components { - margin-left: auto; - margin-right: auto; - } - .drop-empty-rules:hover, - .group:hover .apply-group { - font-weight: 700; - } - .apply-dark-mode:where(.dark, .dark *) { - font-weight: 700; - } - .apply-with-existing:hover { - font-weight: 400; - } - @media (min-width: 640px) { - .apply-with-existing:hover { - background-color: #22c55e; - } - } - .multiple, - .selectors { - font-weight: 700; - } - .group:hover .multiple, - .group:hover .selectors { - font-weight: 400; - } - .list > :not([hidden]) ~ :not([hidden]) { - --tw-space-y-reverse: 0; - margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); - margin-bottom: calc(1rem * var(--tw-space-y-reverse)); - } - .nested .example { - font-weight: 700; - } - .nested .example:hover { - font-weight: 400; - } - .apply-order-a, - .apply-order-b { - margin: 1.5rem 1.25rem 1.25rem; - } - .group:hover .apply-dark-group-example-a:where(.dark, .dark *) { - background-color: #22c55e; - } - @media (min-width: 640px) { - @media (prefers-reduced-motion: no-preference) { - .group:active .crazy-example:focus { - opacity: 0.1; - } - } - } - h1 { - font-size: 1.5rem; - font-weight: 700; - } - h1:first-child { - margin-top: 0; - } - div { - background: #654321; - } - ${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; - } - } - .test-apply-font-variant { - --tw-ordinal: ordinal; - --tw-numeric-spacing: tabular-nums; - font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) - var(--tw-numeric-spacing) var(--tw-numeric-fraction); - } - .custom-component { - background: #123456; - } - *, - :before, - :after, - ::backdrop { - padding: 5px; - } - .foo .bg-black { - appearance: none; - } - .inset-6 { - inset: 1.5rem; - } - .inset-x-1 { - left: 0.25rem; - right: 0.25rem; - } - .end-8 { - inset-inline-end: 2rem; - } - .start-4 { - inset-inline-start: 1rem; - } - .mx-1 { - margin-left: 0.25rem; - margin-right: 0.25rem; - } - .me-8 { - margin-inline-end: 2rem; - } - .ms-4 { - margin-inline-start: 1rem; - } - .mt-6 { - margin-top: 1.5rem; - } - .line-clamp-2 { - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - display: -webkit-box; - overflow: hidden; - } - .line-clamp-\[33\] { - -webkit-line-clamp: 33; - -webkit-box-orient: vertical; - display: -webkit-box; - overflow: hidden; - } - .line-clamp-\[var\(--line-clamp-variable\)\] { - -webkit-line-clamp: var(--line-clamp-variable); - -webkit-box-orient: vertical; - display: -webkit-box; - overflow: hidden; - } - .line-clamp-none { - -webkit-line-clamp: none; - -webkit-box-orient: horizontal; - display: block; - overflow: visible; - } - .scale-50 { - --tw-scale-x: 0.5; - --tw-scale-y: 0.5; - 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)); - } - .transform { - 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)); - } - .grid-cols-\[200px\,repeat\(auto-fill\,minmax\(15\%\,100px\)\)\,300px\] { - grid-template-columns: 200px repeat(auto-fill, minmax(15%, 100px)) 300px; - } - .rounded-e { - border-start-end-radius: 0.25rem; - border-end-end-radius: 0.25rem; - } - .rounded-s { - border-start-start-radius: 0.25rem; - border-end-start-radius: 0.25rem; - } - .rounded-es { - border-end-start-radius: 0.25rem; - } - .rounded-ss { - border-start-start-radius: 0.25rem; - } - .border-2 { - border-width: 2px; - } - .border-e-4 { - border-inline-end-width: 4px; - } - .border-s-0 { - border-inline-start-width: 0; - } - .border-black { - border-color: #000; - } - .border-e-red-400 { - border-inline-end-color: #f87171; - } - .border-s-green-500 { - border-inline-start-color: #22c55e; - } - .bg-black { - background-color: #000; - } - .bg-green-500 { - background-color: #22c55e; - } - .bg-gradient-to-r { - background-image: linear-gradient(to right, var(--tw-gradient-stops)); - } - .bg-hero--home-1 { - background-image: url('/images/homepage-1.jpg'); - } - .from-foo { - --tw-gradient-from: #bada55 var(--tw-gradient-from-position); - --tw-gradient-to: #bada5500 var(--tw-gradient-to-position); - --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); - } - .px-1 { - padding-left: 0.25rem; - padding-right: 0.25rem; - } - .pe-8 { - padding-inline-end: 2rem; - } - .ps-4 { - padding-inline-start: 1rem; - } - .pt-6 { - padding-top: 1.5rem; - } - .text-center { - text-align: center; - } - .font-medium { - font-weight: 500; - } - .shadow-md { - --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; - --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), - 0 2px 4px -2px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), - var(--tw-shadow); - } - .shadow-sm { - --tw-shadow: 0 1px 2px 0 #0000000d; - --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), - var(--tw-shadow); - } - .magic-none { - magic: none; - } - .magic-tons { - magic: tons; - } - .custom-util { - background: #abcdef; - } - *, - :before, - :after, - ::backdrop { - margin: 10px; - } - .hover\:container:hover { - width: 100%; - } - @media (min-width: 640px) { - .hover\:container:hover { - max-width: 640px; - } - } - @media (min-width: 768px) { - .hover\:container:hover { - max-width: 768px; - } - } - @media (min-width: 1024px) { - .hover\:container:hover { - max-width: 1024px; - } - } - @media (min-width: 1280px) { - .hover\:container:hover { - max-width: 1280px; - } - } - @media (min-width: 1536px) { - .hover\:container:hover { - max-width: 1536px; - } - } - @media (min-width: 640px) { - .sm\:container { - width: 100%; - } - @media (min-width: 640px) { - .sm\:container { - max-width: 640px; - } - } - @media (min-width: 768px) { - .sm\:container { - max-width: 768px; - } - } - @media (min-width: 1024px) { - .sm\:container { - max-width: 1024px; - } - } - @media (min-width: 1280px) { - .sm\:container { - max-width: 1280px; - } - } - @media (min-width: 1536px) { - .sm\:container { - max-width: 1536px; - } - } - } - @media (min-width: 768px) { - .md\:container { - width: 100%; - } - @media (min-width: 640px) { - .md\:container { - max-width: 640px; - } - } - @media (min-width: 768px) { - .md\:container { - max-width: 768px; - } - } - @media (min-width: 1024px) { - .md\:container { - max-width: 1024px; - } - } - @media (min-width: 1280px) { - .md\:container { - max-width: 1280px; - } - } - @media (min-width: 1536px) { - .md\:container { - max-width: 1536px; - } - } - } - .first\:pt-0:first-child { - padding-top: 0; - } - .hover\:scale-75:hover { - --tw-scale-x: 0.75; - --tw-scale-y: 0.75; - 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)); - } - .hover\:font-bold:hover { - font-weight: 700; - } - .hover\:shadow-lg:hover { - --tw-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a; - --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), - 0 4px 6px -4px 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\:custom-util:hover { - background: #abcdef; - } - .focus\:font-normal:focus { - font-weight: 400; - } - .focus\:ring-2:focus { - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) - var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) - var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), - var(--tw-shadow, 0 0 #0000); - } - .focus\:ring-blue-500:focus { - --tw-ring-color: #3b82f6; - } - .focus\:hover\:font-light:hover:focus { - font-weight: 300; - } - .disabled\:font-bold:disabled { - font-weight: 700; - } - .group:hover .group-hover\:opacity-100 { - opacity: 1; - } - .group:hover .group-hover\:custom-util { - background: #abcdef; - } - .group:active .group-active\:opacity-10 { - opacity: 0.1; - } - .foo\:custom-util, - .foo\:hover\:custom-util:hover { - background: #abcdef !important; + } + @media (min-width: 640px) and (max-width: 767px), (max-width: 868px) { + .multi\:text-left { + text-align: left; } + } + .dark\:custom-util:where(.dark, .dark *) { + background: #abcdef; + } + @media (min-width: 768px) { @media (prefers-reduced-motion: no-preference) { - .motion-safe\:transition { - transition-property: color, background-color, border-color, text-decoration-color, fill, - stroke, opacity, box-shadow, transform, filter, backdrop-filter; - transition-duration: 0.15s; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - } - .motion-safe\:custom-util { - background: #abcdef; - } - } - @media (prefers-reduced-motion: reduce) { - .motion-reduce\:transition { - transition-property: color, background-color, border-color, text-decoration-color, fill, - stroke, opacity, box-shadow, transform, filter, backdrop-filter; - transition-duration: 0.15s; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - } - } - @media (min-width: 640px) { - .sm\:text-center { - text-align: center; - } - .sm\:tabular-nums { - --tw-numeric-spacing: tabular-nums; - font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) - var(--tw-numeric-spacing) var(--tw-numeric-fraction); - } - .sm\:custom-util { - background: #abcdef; - } - @media (prefers-reduced-motion: no-preference) { - .group:active .sm\:motion-safe\:group-active\:focus\:opacity-10:focus { - opacity: 0.1; - } - } - } - @media (min-width: 768px) { - .md\:text-center { - text-align: center; - } - .md\:opacity-50 { - opacity: 0.5; - } - .md\:shadow-sm { - --tw-shadow: 0 1px 2px 0 #0000000d; - --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), - var(--tw-shadow); - } - .md\:hover\:border-r-blue-500\/30:hover { - border-right-color: #3b82f64d; - } - .md\:hover\:opacity-20:hover { - opacity: 0.2; - } - @media (prefers-reduced-motion: no-preference) { - .md\:motion-safe\:hover\:transition:hover { - transition-property: color, background-color, border-color, text-decoration-color, - fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; - transition-duration: 0.15s; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - } - } - @media (min-width: 640px) { - .md\:sm\:text-center { - text-align: center; - } - } - } - @media (min-width: 1280px) and (max-width: 1535px) { - .range\:text-right { - text-align: right; - } - } - @media (min-width: 640px) and (max-width: 767px), (max-width: 868px) { - .multi\:text-left { - text-align: left; - } - } - .dark\:custom-util:where(.dark, .dark *) { - background: #abcdef; - } - @media (min-width: 768px) { - @media (prefers-reduced-motion: no-preference) { .md\:dark\:motion-safe\:foo\:active\:custom-util:active:where(.dark, .dark *) { background: #abcdef !important; } } - `) - }) + } + `) }) }) diff --git a/tests/layer-at-rules.test.js b/tests/layer-at-rules.test.js index db98bbeb56e8..1c4604835f09 100644 --- a/tests/layer-at-rules.test.js +++ b/tests/layer-at-rules.test.js @@ -1,321 +1,319 @@ -import { crosscheck, run, html, css, defaults } from './util/run' - -crosscheck(() => { - test('custom user-land utilities', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - theme: {}, - plugins: [], - } - - let input = css` - @layer utilities { - .align-banana { - text-align: banana; - } +import { run, html, css, defaults } from './util/run' + +test('custom user-land utilities', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + theme: {}, + plugins: [], + } + + let input = css` + @layer utilities { + .align-banana { + text-align: banana; } + } - @tailwind base; - @tailwind components; - @tailwind utilities; + @tailwind base; + @tailwind components; + @tailwind utilities; - @layer utilities { - .align-chocolate { - text-align: chocolate; - } + @layer utilities { + .align-chocolate { + text-align: chocolate; } - ` + } + ` - 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; - } - `) - }) + 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; + } + `) }) +}) - test('comments can be used inside layers without crashing', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - theme: {}, - plugins: [], +test('comments can be used inside layers without crashing', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + 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, + ${defaults} + .important-component, .important-utility { - text-align: banana; - } - `) - }) + text-align: banana; + } + `) }) +}) - test('comments can be used inside layers (with important) without crashing', () => { - let config = { - important: true, - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - theme: {}, - plugins: [], +test('comments can be used inside layers (with important) without crashing', () => { + let config = { + important: true, + content: [ + { + raw: html`
`, + }, + ], + 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`
`, - }, - ], - plugins: [ - function ({ addBase, addComponents, addUtilities }) { - addBase({ body: { margin: 0 } }) - - addComponents({ - '.input': { background: 'white' }, - }) - - addUtilities({ - '.float-squirrel': { float: 'squirrel' }, - }) - }, - ], - corePlugins: { preflight: false }, +test('layers are grouped and inserted at the matching @tailwind rule', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + plugins: [ + function ({ addBase, addComponents, addUtilities }) { + addBase({ body: { margin: 0 } }) + + addComponents({ + '.input': { background: 'white' }, + }) + + addUtilities({ + '.float-squirrel': { float: 'squirrel' }, + }) + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @layer vanilla { + strong { + font-weight: medium; + } } - let input = css` - @layer vanilla { - strong { - font-weight: medium; - } + @tailwind base; + @tailwind components; + @tailwind utilities; + + @layer components { + .btn { + background: blue; } + } - @tailwind base; - @tailwind components; - @tailwind utilities; + @layer utilities { + .align-banana { + text-align: banana; + } + } - @layer components { - .btn { - background: blue; - } + @layer base { + h1 { + font-weight: bold; } + } - @layer utilities { - .align-banana { - text-align: banana; - } + @layer components { + .card { + border-radius: 12px; } + } - @layer base { - h1 { - font-weight: bold; - } + @layer base { + p { + font-weight: normal; } + } - @layer components { - .card { - border-radius: 12px; - } + @layer utilities { + .align-sandwich { + text-align: sandwich; } + } - @layer base { - p { - font-weight: normal; - } + @layer chocolate { + a { + text-decoration: underline; } + } + ` + + expect.assertions(2) - @layer utilities { - .align-sandwich { - text-align: sandwich; + 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; } } - ` - - 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`
` }], - plugins: [], - } +it('should keep `@supports` rules inside `@layer`s', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + } - let input = css` - @tailwind utilities; + let input = css` + @tailwind utilities; - @layer utilities { - .test { - --tw-test: 1; - } + @layer utilities { + .test { + --tw-test: 1; + } - @supports (backdrop-filter: blur(1px)) { - .test { - --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` + return run(input, config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .test { + --tw-test: 1; + } + @supports (backdrop-filter: blur(1px)) { .test { - --tw-test: 1; - } - @supports (backdrop-filter: blur(1px)) { - .test { - --tw-test: 0.9; - } + --tw-test: 0.9; } - `) - }) + } + `) }) }) diff --git a/tests/layer-without-tailwind.test.js b/tests/layer-without-tailwind.test.js index f15d4335161d..4122873c9515 100644 --- a/tests/layer-without-tailwind.test.js +++ b/tests/layer-without-tailwind.test.js @@ -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`
` }], - } +test('using @layer without @tailwind', async () => { + let config = { + content: [{ raw: html`
` }], + } - 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.' - ) - }) + 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`
` }], - } +test('using @responsive without @tailwind', async () => { + let config = { + content: [{ raw: html`
` }], + } - let input = css` - @responsive { - .foo { - color: black; - } + let input = css` + @responsive { + .foo { + color: black; } - ` + } + ` - await expect(run(input, config)).rejects.toThrowError( - '`@responsive` is used but `@tailwind utilities` is missing.' - ) - }) + 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`
` }], - } +test('using @variants without @tailwind', async () => { + let config = { + content: [{ raw: html`
` }], + } - let input = css` - @variants hover { - .foo { - color: black; - } + let input = css` + @variants hover { + .foo { + color: black; } - ` + } + ` - await expect(run(input, config)).rejects.toThrowError( - '`@variants` is used but `@tailwind utilities` is missing.' - ) - }) + 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`
` }], + } - test('non-Tailwind @layer rules are okay', async () => { - let config = { - content: [{ raw: html`
` }], + let input = css` + @layer custom { + .foo { + color: black; + } } + ` - let input = css` + 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; - } - } - `) - }) + `) }) }) diff --git a/tests/match-components.test.js b/tests/match-components.test.js index a8b546127bc6..8f62417248fd 100644 --- a/tests/match-components.test.js +++ b/tests/match-components.test.js @@ -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`
-
-
- -
`, - }, - ], - corePlugins: { - preflight: false, +it('should be possible to matchComponents', () => { + let config = { + content: [ + { + raw: html`
+
+
+ +
`, }, - 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; } - ) + `) }) }) diff --git a/tests/match-utilities.test.js b/tests/match-utilities.test.js index 9b3cd4c56ec4..41ef29a290aa 100644 --- a/tests/match-utilities.test.js +++ b/tests/match-utilities.test.js @@ -1,575 +1,573 @@ -import { crosscheck, run, html, css } from './util/run' - -crosscheck(() => { - test('match utilities with modifiers', async () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - - plugins: [ - ({ matchUtilities }) => { - matchUtilities( - { - test: (value, { modifier }) => ({ - color: `${value}_${modifier}`, - }), +import { run, html, css } from './util/run' + +test('match utilities with modifiers', async () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + + plugins: [ + ({ matchUtilities }) => { + matchUtilities( + { + test: (value, { modifier }) => ({ + color: `${value}_${modifier}`, + }), + }, + { + values: { + DEFAULT: 'default', + bar: 'bar', + '1': 'one', + '2': 'two', + '1/foo': 'onefoo', + '[8]/[9]': 'eightnine', }, - { - values: { - DEFAULT: 'default', - bar: 'bar', - '1': 'one', - '2': 'two', - '1/foo': 'onefoo', - '[8]/[9]': 'eightnine', - }, - modifiers: 'any', - } - ) - }, - ], + modifiers: 'any', + } + ) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + let result = await run(input, config) + + expect(result.css).toMatchFormattedCss(css` + .test { + color: default_null; } + .test-1\/\[foo\] { + color: one_[foo]; + } + .test-1\/foo { + color: onefoo_null; + } + .test-2\/foo { + color: two_foo; + } + .test-\[8\]\/\[9\] { + color: eightnine_null; + } + .test\/\[foo\] { + color: default_[foo]; + } + .test\/foo { + color: default_foo; + } + `) +}) - let input = css` - @tailwind utilities; - ` - - let result = await run(input, config) - - expect(result.css).toMatchFormattedCss(css` - .test { - color: default_null; - } - .test-1\/\[foo\] { - color: one_[foo]; - } - .test-1\/foo { - color: onefoo_null; - } - .test-2\/foo { - color: two_foo; - } - .test-\[8\]\/\[9\] { - color: eightnine_null; - } - .test\/\[foo\] { - color: default_[foo]; - } - .test\/foo { - color: default_foo; - } - `) - }) - - test('match utilities with modifiers in the config', async () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - - plugins: [ - ({ matchUtilities }) => { - matchUtilities( - { - test: (value, { modifier }) => ({ - color: `${value}_${modifier}`, - }), +test('match utilities with modifiers in the config', async () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + + plugins: [ + ({ matchUtilities }) => { + matchUtilities( + { + test: (value, { modifier }) => ({ + color: `${value}_${modifier}`, + }), + }, + { + values: { + DEFAULT: 'default', + bar: 'bar', + '1': 'one', }, - { - values: { - DEFAULT: 'default', - bar: 'bar', - '1': 'one', - }, - modifiers: { - foo: 'mewtwo', - }, - } - ) - }, - ], - } + modifiers: { + foo: 'mewtwo', + }, + } + ) + }, + ], + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - let result = await run(input, config) + let result = await run(input, config) - expect(result.css).toMatchFormattedCss(css` - .test { - color: default_null; - } - .test-1\/\[bar\] { - color: one_bar; - } - .test-1\/foo { - color: one_mewtwo; - } - .test\/\[bar\] { - color: default_bar; - } - .test\/foo { - color: default_mewtwo; - } - `) - }) + expect(result.css).toMatchFormattedCss(css` + .test { + color: default_null; + } + .test-1\/\[bar\] { + color: one_bar; + } + .test-1\/foo { + color: one_mewtwo; + } + .test\/\[bar\] { + color: default_bar; + } + .test\/foo { + color: default_mewtwo; + } + `) +}) - test('match utilities can omit utilities by returning null', async () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - - plugins: [ - ({ matchUtilities }) => { - matchUtilities( - { - test: (value, { modifier }) => - modifier === 'bad' - ? null - : { - color: `${value}_${modifier}`, - }, +test('match utilities can omit utilities by returning null', async () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + + plugins: [ + ({ matchUtilities }) => { + matchUtilities( + { + test: (value, { modifier }) => + modifier === 'bad' + ? null + : { + color: `${value}_${modifier}`, + }, + }, + { + values: { + DEFAULT: 'default', + bar: 'bar', + '1': 'one', }, - { - values: { - DEFAULT: 'default', - bar: 'bar', - '1': 'one', - }, - modifiers: 'any', - } - ) - }, - ], + modifiers: 'any', + } + ) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + let result = await run(input, config) + + expect(result.css).toMatchFormattedCss(css` + .test { + color: default_null; } + .test\/good { + color: default_good; + } + `) +}) - let input = css` - @tailwind utilities; - ` - - let result = await run(input, config) - - expect(result.css).toMatchFormattedCss(css` - .test { - color: default_null; - } - .test\/good { - color: default_good; +test('matching utilities with a basic configured value', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value) => ({ value }), + }, + { + values: { + foo: 'value_foo', + }, + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-foo { + value: value_foo; } `) }) +}) - test('matching utilities with a basic configured value', () => { - let config = { - content: [{ raw: html`
` }], - theme: {}, - plugins: [ - function ({ matchUtilities }) { - matchUtilities( - { - test: (value) => ({ value }), +test('matching utilities with an arbitrary value and configured modifier', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + modifiers: { + bar: 'configured_bar', }, - { - values: { - foo: 'value_foo', - }, - } - ) - }, - ], - corePlugins: [], - } - - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchCss(css` - .test-foo { - value: value_foo; - } - `) - }) + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-\[foo\]\/bar { + value: foo; + modifier: configured_bar; + } + `) }) +}) - test('matching utilities with an arbitrary value and configured modifier', () => { - let config = { - content: [{ raw: html`
` }], - theme: {}, - plugins: [ - function ({ matchUtilities }) { - matchUtilities( - { - test: (value, { modifier }) => ({ value, modifier }), +test('matching utilities with an configured value and an arbitrary modifier (raw)', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + foo: 'configured_foo', }, - { - modifiers: { - bar: 'configured_bar', - }, - } - ) - }, - ], - corePlugins: [], - } - - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchCss(css` - .test-\[foo\]\/bar { - value: foo; - modifier: configured_bar; - } - `) - }) + modifiers: 'any', // Raw `[value]` + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-foo\/\[bar\] { + value: configured_foo; + modifier: [bar]; + } + `) }) +}) - test('matching utilities with an configured value and an arbitrary modifier (raw)', () => { - let config = { - content: [{ raw: html`
` }], - theme: {}, - plugins: [ - function ({ matchUtilities }) { - matchUtilities( - { - test: (value, { modifier }) => ({ value, modifier }), +test('matching utilities with an configured value and an arbitrary modifier (non-raw)', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + foo: 'configured_foo', }, - { - values: { - foo: 'configured_foo', - }, - modifiers: 'any', // Raw `[value]` - } - ) - }, - ], - corePlugins: [], - } - - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchCss(css` - .test-foo\/\[bar\] { - value: configured_foo; - modifier: [bar]; - } - `) - }) + modifiers: {}, + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-foo\/\[bar\] { + value: configured_foo; + modifier: bar; + } + `) }) +}) - test('matching utilities with an configured value and an arbitrary modifier (non-raw)', () => { - let config = { - content: [{ raw: html`
` }], - theme: {}, - plugins: [ - function ({ matchUtilities }) { - matchUtilities( - { - test: (value, { modifier }) => ({ value, modifier }), +test('matching utilities with an configured value and a configured modifier', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + foo: 'configured_foo', }, - { - values: { - foo: 'configured_foo', - }, - modifiers: {}, - } - ) - }, - ], - corePlugins: [], - } - - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchCss(css` - .test-foo\/\[bar\] { - value: configured_foo; - modifier: bar; - } - `) - }) - }) - - test('matching utilities with an configured value and a configured modifier', () => { - let config = { - content: [{ raw: html`
` }], - theme: {}, - plugins: [ - function ({ matchUtilities }) { - matchUtilities( - { - test: (value, { modifier }) => ({ value, modifier }), + modifiers: { + bar: 'configured_bar', }, - { - values: { - foo: 'configured_foo', - }, - modifiers: { - bar: 'configured_bar', - }, - } - ) - }, - ], - corePlugins: [], - } - - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchCss(css` - .test-foo\/bar { - value: configured_foo; - modifier: configured_bar; - } - `) - }) + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-foo\/bar { + value: configured_foo; + modifier: configured_bar; + } + `) }) +}) - test('matching utilities with an arbitrary value and an arbitrary modifier (raw)', () => { - let config = { - content: [{ raw: html`
` }], - theme: {}, - plugins: [ - function ({ matchUtilities }) { - matchUtilities( - { - test: (value, { modifier }) => ({ value, modifier }), - }, - { - modifiers: 'any', - } - ) - }, - ], - corePlugins: [], - } - - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchCss(css` - .test-\[foo\]\/\[bar\] { - value: foo; - modifier: [bar]; - } - `) - }) +test('matching utilities with an arbitrary value and an arbitrary modifier (raw)', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + modifiers: 'any', + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-\[foo\]\/\[bar\] { + value: foo; + modifier: [bar]; + } + `) }) +}) - test('matching utilities with an arbitrary value and an arbitrary modifier (non-raw)', () => { - let config = { - content: [{ raw: html`
` }], - theme: {}, - plugins: [ - function ({ matchUtilities }) { - matchUtilities( - { - test: (value, { modifier }) => ({ value, modifier }), - }, - { - modifiers: {}, - } - ) - }, - ], - corePlugins: [], - } - - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchCss(css` - .test-\[foo\]\/\[bar\] { - value: foo; - modifier: bar; - } - `) - }) +test('matching utilities with an arbitrary value and an arbitrary modifier (non-raw)', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + modifiers: {}, + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-\[foo\]\/\[bar\] { + value: foo; + modifier: bar; + } + `) }) +}) - test('matching utilities with a lookup value that looks like an arbitrary value and modifier', () => { - let config = { - content: [{ raw: html`
` }], - theme: {}, - plugins: [ - function ({ matchUtilities }) { - matchUtilities( - { - test: (value, { modifier }) => ({ value, modifier }), +test('matching utilities with a lookup value that looks like an arbitrary value and modifier', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + '[foo]/[bar]': 'hello', }, - { - values: { - '[foo]/[bar]': 'hello', - }, - } - ) - }, - ], - corePlugins: [], - } - - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchCss(css` - .test-\[foo\]\/\[bar\] { - value: hello; - } - `) - }) + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-\[foo\]\/\[bar\] { + value: hello; + } + `) }) +}) - test('matching utilities with a lookup value that looks like an arbitrary value and modifier (with modifiers = any)', () => { - let config = { - content: [{ raw: html`
` }], - theme: {}, - plugins: [ - function ({ matchUtilities }) { - matchUtilities( - { - test: (value, { modifier }) => ({ value, modifier }), +test('matching utilities with a lookup value that looks like an arbitrary value and modifier (with modifiers = any)', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + '[foo]/[bar]': 'hello', }, - { - values: { - '[foo]/[bar]': 'hello', - }, - modifiers: 'any', - } - ) - }, - ], - corePlugins: [], - } - - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchCss(css` - .test-\[foo\]\/\[bar\] { - value: hello; - } - `) - }) + modifiers: 'any', + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-\[foo\]\/\[bar\] { + value: hello; + } + `) }) +}) - test('matching utilities with a lookup value that looks like an arbitrary value and modifier (with modifiers = {})', () => { - let config = { - content: [{ raw: html`
` }], - theme: {}, - plugins: [ - function ({ matchUtilities }) { - matchUtilities( - { - test: (value, { modifier }) => ({ value, modifier }), +test('matching utilities with a lookup value that looks like an arbitrary value and modifier (with modifiers = {})', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + '[foo]/[bar]': 'hello', }, - { - values: { - '[foo]/[bar]': 'hello', - }, - modifiers: {}, - } - ) - }, - ], - corePlugins: [], - } - - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchCss(css` - .test-\[foo\]\/\[bar\] { - value: hello; - } - `) - }) + modifiers: {}, + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-\[foo\]\/\[bar\] { + value: hello; + } + `) }) +}) - test('matching utilities with a lookup value that looks like an arbitrary value and a configured modifier', () => { - let config = { - content: [{ raw: html`
` }], - theme: {}, - plugins: [ - function ({ matchUtilities }) { - matchUtilities( - { - test: (value, { modifier }) => ({ value, modifier }), +test('matching utilities with a lookup value that looks like an arbitrary value and a configured modifier', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + '[foo]/bar': 'hello', }, - { - values: { - '[foo]/bar': 'hello', - }, - } - ) - }, - ], - corePlugins: [], - } - - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchCss(css` - .test-\[foo\]\/bar { - value: hello; - } - `) - }) + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-\[foo\]\/bar { + value: hello; + } + `) }) +}) - test('matching utilities with a lookup value that looks like a configured value and an arbitrary modifier', () => { - let config = { - content: [{ raw: html`
` }], - theme: {}, - plugins: [ - function ({ matchUtilities }) { - matchUtilities( - { - test: (value, { modifier }) => ({ value, modifier }), +test('matching utilities with a lookup value that looks like a configured value and an arbitrary modifier', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + 'foo/[bar]': 'hello', }, - { - values: { - 'foo/[bar]': 'hello', - }, - } - ) - }, - ], - corePlugins: [], - } - - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchCss(css` - .test-foo\/\[bar\] { - value: hello; - } - `) - }) + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-foo\/\[bar\] { + value: hello; + } + `) }) +}) - test('matching utilities with a lookup value that does not match the configured type', () => { - let config = { - content: [{ raw: html`
` }], - theme: {}, - plugins: [ - function ({ matchUtilities }) { - matchUtilities( - { - test: (value, { modifier }) => ({ value, modifier }), +test('matching utilities with a lookup value that does not match the configured type', () => { + let config = { + content: [{ raw: html`
` }], + theme: {}, + plugins: [ + function ({ matchUtilities }) { + matchUtilities( + { + test: (value, { modifier }) => ({ value, modifier }), + }, + { + values: { + foo: 'not-a-percentage', }, - { - values: { - foo: 'not-a-percentage', - }, - type: ['percentage'], - } - ) - }, - ], - corePlugins: [], - } - - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchCss(css` - .test-foo { - value: not-a-percentage; - } - `) - }) + type: ['percentage'], + } + ) + }, + ], + corePlugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchCss(css` + .test-foo { + value: not-a-percentage; + } + `) }) }) diff --git a/tests/match-variants.test.js b/tests/match-variants.test.js index e4330f7a92cc..2923b10c0dc2 100644 --- a/tests/match-variants.test.js +++ b/tests/match-variants.test.js @@ -1,856 +1,820 @@ import resolveConfig from '../src/public/resolve-config' import { createContext } from '../src/lib/setupContextUtils' -import { crosscheck, run, html, css } from './util/run' - -crosscheck(({ stable, oxide }) => { - test('partial arbitrary variants', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - ({ matchVariant }) => { - matchVariant('potato', (flavor) => `.potato-${flavor} &`) - }, - ], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .potato-baked .potato-\[baked\]\:w-3 { +import { run, html, css } from './util/run' + +test('partial arbitrary variants', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('potato', (flavor) => `.potato-${flavor} &`) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .potato-baked .potato-\[baked\]\:w-3 { + width: 0.75rem; + } + .potato-yellow .potato-\[yellow\]\:bg-yellow-200 { + --tw-bg-opacity: 1; + background-color: rgb(254 240 138 / var(--tw-bg-opacity)); + } + `) + }) +}) + +test('partial arbitrary variants with at-rules', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('potato', (flavor) => `@media (potato: ${flavor})`) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + @media (potato: baked) { + .potato-\[baked\]\:w-3 { width: 0.75rem; } - .potato-yellow .potato-\[yellow\]\:bg-yellow-200 { + } + @media (potato: yellow) { + .potato-\[yellow\]\:bg-yellow-200 { --tw-bg-opacity: 1; background-color: rgb(254 240 138 / var(--tw-bg-opacity)); } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .potato-baked .potato-\[baked\]\:w-3 { + } + `) + }) +}) + +test('partial arbitrary variants with at-rules and placeholder', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('potato', (flavor) => `@media (potato: ${flavor}) { &:potato }`) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + @media (potato: baked) { + .potato-\[baked\]\:w-3:potato { width: 0.75rem; } - .potato-yellow .potato-\[yellow\]\:bg-yellow-200 { - background-color: #fef08a; + } + @media (potato: yellow) { + .potato-\[yellow\]\:bg-yellow-200:potato { + --tw-bg-opacity: 1; + background-color: rgb(254 240 138 / var(--tw-bg-opacity)); } - `) - }) + } + `) }) +}) - test('partial arbitrary variants with at-rules', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - ({ matchVariant }) => { - matchVariant('potato', (flavor) => `@media (potato: ${flavor})`) - }, - ], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - @media (potato: baked) { - .potato-\[baked\]\:w-3 { - width: 0.75rem; - } - } - @media (potato: yellow) { - .potato-\[yellow\]\:bg-yellow-200 { - --tw-bg-opacity: 1; - background-color: rgb(254 240 138 / var(--tw-bg-opacity)); - } - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - @media (potato: baked) { - .potato-\[baked\]\:w-3 { - width: 0.75rem; - } - } - @media (potato: yellow) { - .potato-\[yellow\]\:bg-yellow-200 { - background-color: #fef08a; - } - } - `) - }) +test('partial arbitrary variants with default values', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('tooltip', (side) => `&${side}`, { + values: { + bottom: '[data-location="bottom"]', + top: '[data-location="top"]', + }, + }) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .tooltip-bottom\:mt-2[data-location='bottom'] { + margin-top: 0.5rem; + } + .tooltip-top\:mb-2[data-location='top'] { + margin-bottom: 0.5rem; + } + `) }) +}) - test('partial arbitrary variants with at-rules and placeholder', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - ({ matchVariant }) => { - matchVariant('potato', (flavor) => `@media (potato: ${flavor}) { &:potato }`) - }, - ], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - @media (potato: baked) { - .potato-\[baked\]\:w-3:potato { - width: 0.75rem; - } - } - @media (potato: yellow) { - .potato-\[yellow\]\:bg-yellow-200:potato { - --tw-bg-opacity: 1; - background-color: rgb(254 240 138 / var(--tw-bg-opacity)); - } - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - @media (potato: baked) { - .potato-\[baked\]\:w-3:potato { - width: 0.75rem; - } - } - @media (potato: yellow) { - .potato-\[yellow\]\:bg-yellow-200:potato { - background-color: #fef08a; - } - } - `) - }) +test('matched variant values maintain the sort order they are registered in', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('alphabet', (side) => `&${side}`, { + values: { + a: '[data-value="a"]', + b: '[data-value="b"]', + c: '[data-value="c"]', + d: '[data-value="d"]', + }, + }) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .alphabet-a\:underline[data-value='a'], + .alphabet-b\:underline[data-value='b'], + .alphabet-c\:underline[data-value='c'], + .alphabet-d\:underline[data-value='d'] { + text-decoration-line: underline; + } + `) }) +}) - test('partial arbitrary variants with default values', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - ({ matchVariant }) => { - matchVariant('tooltip', (side) => `&${side}`, { - values: { - bottom: '[data-location="bottom"]', - top: '[data-location="top"]', - }, - }) - }, - ], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .tooltip-bottom\:mt-2[data-location='bottom'] { - margin-top: 0.5rem; - } - .tooltip-top\:mb-2[data-location='top'] { - margin-bottom: 0.5rem; - } - `) - }) +test('matchVariant can return an array of format strings from the function', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('test', (selector) => + selector.split(',').map((selector) => `&.${selector} > *`) + ) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .test-\[a\,b\,c\]\:underline.a > *, + .test-\[a\,b\,c\]\:underline.b > *, + .test-\[a\,b\,c\]\:underline.c > * { + text-decoration-line: underline; + } + `) }) +}) - test('matched variant values maintain the sort order they are registered in', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - ({ matchVariant }) => { - matchVariant('alphabet', (side) => `&${side}`, { - values: { - a: '[data-value="a"]', - b: '[data-value="b"]', - c: '[data-value="c"]', - d: '[data-value="d"]', - }, - }) - }, - ], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .alphabet-a\:underline[data-value='a'], - .alphabet-b\:underline[data-value='b'], - .alphabet-c\:underline[data-value='c'], - .alphabet-d\:underline[data-value='d'] { +it('should be possible to sort variants', () => { + let config = { + content: [ + { + raw: html` +
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('testmin', (value) => `@media (min-width: ${value})`, { + sort(a, z) { + return parseInt(a.value) - parseInt(z.value) + }, + }) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + @media (min-width: 500px) { + .testmin-\[500px\]\:underline { text-decoration-line: underline; } - `) - }) + } + @media (min-width: 700px) { + .testmin-\[700px\]\:italic { + font-style: italic; + } + } + `) }) +}) - test('matchVariant can return an array of format strings from the function', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - ({ matchVariant }) => { - matchVariant('test', (selector) => - selector.split(',').map((selector) => `&.${selector} > *`) - ) - }, - ], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .test-\[a\,b\,c\]\:underline.a > *, - .test-\[a\,b\,c\]\:underline.b > *, - .test-\[a\,b\,c\]\:underline.c > * { - text-decoration-line: underline; - } - `) - }) +it('should be possible to compare arbitrary variants and hardcoded variants', () => { + let config = { + content: [ + { + raw: html` +
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('testmin', (value) => `@media (min-width: ${value})`, { + values: { + example: '600px', + }, + sort(a, z) { + return parseInt(a.value) - parseInt(z.value) + }, + }) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + @media (min-width: 500px) { + .testmin-\[500px\]\:italic { + font-style: italic; + } + } + @media (min-width: 600px) { + .testmin-example\:italic { + font-style: italic; + } + } + @media (min-width: 700px) { + .testmin-\[700px\]\:italic { + font-style: italic; + } + } + `) }) +}) - it('should be possible to sort variants', () => { - let config = { - content: [ - { - raw: html` -
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - ({ matchVariant }) => { - matchVariant('testmin', (value) => `@media (min-width: ${value})`, { - sort(a, z) { - return parseInt(a.value) - parseInt(z.value) - }, - }) - }, - ], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - @media (min-width: 500px) { - .testmin-\[500px\]\:underline { +it('should be possible to sort stacked arbitrary variants correctly', () => { + let config = { + content: [ + { + raw: html` +
+ +
+ +
+ +
+ +
+
+ `, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('testmin', (value) => `@media (min-width: ${value})`, { + sort(a, z) { + return parseInt(a.value) - parseInt(z.value) + }, + }) + + matchVariant('testmax', (value) => `@media (max-width: ${value})`, { + sort(a, z) { + return parseInt(z.value) - parseInt(a.value) + }, + }) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + @media (min-width: 100px) { + @media (max-width: 400px) { + .testmin-\[100px\]\:testmax-\[400px\]\:underline { text-decoration-line: underline; } } - @media (min-width: 700px) { - .testmin-\[700px\]\:italic { - font-style: italic; + @media (max-width: 350px) { + .testmin-\[100px\]\:testmax-\[350px\]\:underline { + text-decoration-line: underline; } } - `) - }) - }) - - it('should be possible to compare arbitrary variants and hardcoded variants', () => { - let config = { - content: [ - { - raw: html` -
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - ({ matchVariant }) => { - matchVariant('testmin', (value) => `@media (min-width: ${value})`, { - values: { - example: '600px', - }, - sort(a, z) { - return parseInt(a.value) - parseInt(z.value) - }, - }) - }, - ], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - @media (min-width: 500px) { - .testmin-\[500px\]\:italic { - font-style: italic; + @media (max-width: 300px) { + .testmin-\[100px\]\:testmax-\[300px\]\:underline { + text-decoration-line: underline; } } - @media (min-width: 600px) { - .testmin-example\:italic { - font-style: italic; + } + @media (min-width: 150px) { + @media (max-width: 400px) { + .testmin-\[150px\]\:testmax-\[400px\]\:underline { + text-decoration-line: underline; } } - @media (min-width: 700px) { - .testmin-\[700px\]\:italic { - font-style: italic; + } + `) + }) +}) + +it('should maintain sort from other variants, if sort functions of arbitrary variants return 0', () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('testmin', (value) => `@media (min-width: ${value})`, { + sort(a, z) { + return parseInt(a.value) - parseInt(z.value) + }, + }) + + matchVariant('testmax', (value) => `@media (max-width: ${value})`, { + sort(a, z) { + return parseInt(z.value) - parseInt(a.value) + }, + }) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + @media (min-width: 100px) { + @media (max-width: 200px) { + .testmin-\[100px\]\:testmax-\[200px\]\:hover\:underline:hover, + .testmin-\[100px\]\:testmax-\[200px\]\:focus\:underline:focus { + text-decoration-line: underline; } } - `) - }) + } + `) }) +}) - it('should be possible to sort stacked arbitrary variants correctly', () => { - let config = { - content: [ - { - raw: html` -
- -
- -
- -
- -
-
- `, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - ({ matchVariant }) => { - matchVariant('testmin', (value) => `@media (min-width: ${value})`, { - sort(a, z) { - return parseInt(a.value) - parseInt(z.value) - }, - }) - - matchVariant('testmax', (value) => `@media (max-width: ${value})`, { - sort(a, z) { - return parseInt(z.value) - parseInt(a.value) - }, - }) - }, - ], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - @media (min-width: 100px) { - @media (max-width: 400px) { - .testmin-\[100px\]\:testmax-\[400px\]\:underline { - text-decoration-line: underline; - } +it('should sort arbitrary variants left to right (1)', () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('testmin', (value) => `@media (min-width: ${value})`, { + sort(a, z) { + return parseInt(a.value) - parseInt(z.value) + }, + }) + matchVariant('testmax', (value) => `@media (max-width: ${value})`, { + sort(a, z) { + return parseInt(z.value) - parseInt(a.value) + }, + }) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + @media (min-width: 100px) { + @media (max-width: 400px) { + .testmin-\[100px\]\:testmax-\[400px\]\:underline { + text-decoration-line: underline; } - @media (max-width: 350px) { - .testmin-\[100px\]\:testmax-\[350px\]\:underline { - text-decoration-line: underline; - } + } + @media (max-width: 300px) { + .testmin-\[100px\]\:testmax-\[300px\]\:underline { + text-decoration-line: underline; } - @media (max-width: 300px) { - .testmin-\[100px\]\:testmax-\[300px\]\:underline { - text-decoration-line: underline; - } + } + } + @media (min-width: 200px) { + @media (max-width: 400px) { + .testmin-\[200px\]\:testmax-\[400px\]\:underline { + text-decoration-line: underline; } } - @media (min-width: 150px) { - @media (max-width: 400px) { - .testmin-\[150px\]\:testmax-\[400px\]\:underline { - text-decoration-line: underline; - } + @media (max-width: 300px) { + .testmin-\[200px\]\:testmax-\[300px\]\:underline { + text-decoration-line: underline; } } - `) - }) + } + `) }) +}) - it('should maintain sort from other variants, if sort functions of arbitrary variants return 0', () => { - let config = { - content: [ - { - raw: html` -
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - ({ matchVariant }) => { - matchVariant('testmin', (value) => `@media (min-width: ${value})`, { - sort(a, z) { - return parseInt(a.value) - parseInt(z.value) - }, - }) - - matchVariant('testmax', (value) => `@media (max-width: ${value})`, { - sort(a, z) { - return parseInt(z.value) - parseInt(a.value) - }, - }) - }, - ], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` +it('should sort arbitrary variants left to right (2)', () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('testmin', (value) => `@media (min-width: ${value})`, { + sort(a, z) { + return parseInt(a.value) - parseInt(z.value) + }, + }) + matchVariant('testmax', (value) => `@media (max-width: ${value})`, { + sort(a, z) { + return parseInt(z.value) - parseInt(a.value) + }, + }) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + @media (max-width: 400px) { @media (min-width: 100px) { - @media (max-width: 200px) { - .testmin-\[100px\]\:testmax-\[200px\]\:hover\:underline:hover, - .testmin-\[100px\]\:testmax-\[200px\]\:focus\:underline:focus { - text-decoration-line: underline; - } + .testmax-\[400px\]\:testmin-\[100px\]\:underline { + text-decoration-line: underline; } } - `) - }) - }) + @media (min-width: 200px) { + .testmax-\[400px\]\:testmin-\[200px\]\:underline { + text-decoration-line: underline; + } + } + } - it('should sort arbitrary variants left to right (1)', () => { - let config = { - content: [ - { - raw: html` -
-
-
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - ({ matchVariant }) => { - matchVariant('testmin', (value) => `@media (min-width: ${value})`, { - sort(a, z) { - return parseInt(a.value) - parseInt(z.value) - }, - }) - matchVariant('testmax', (value) => `@media (max-width: ${value})`, { - sort(a, z) { - return parseInt(z.value) - parseInt(a.value) - }, - }) - }, - ], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` + @media (max-width: 300px) { @media (min-width: 100px) { - @media (max-width: 400px) { - .testmin-\[100px\]\:testmax-\[400px\]\:underline { - text-decoration-line: underline; - } - } - @media (max-width: 300px) { - .testmin-\[100px\]\:testmax-\[300px\]\:underline { - text-decoration-line: underline; - } + .testmax-\[300px\]\:testmin-\[100px\]\:underline { + text-decoration-line: underline; } } @media (min-width: 200px) { - @media (max-width: 400px) { - .testmin-\[200px\]\:testmax-\[400px\]\:underline { - text-decoration-line: underline; - } - } - @media (max-width: 300px) { - .testmin-\[200px\]\:testmax-\[300px\]\:underline { - text-decoration-line: underline; - } + .testmax-\[300px\]\:testmin-\[200px\]\:underline { + text-decoration-line: underline; } } - `) - }) + } + `) }) +}) - it('should sort arbitrary variants left to right (2)', () => { - let config = { - content: [ - { - raw: html` -
-
-
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - ({ matchVariant }) => { - matchVariant('testmin', (value) => `@media (min-width: ${value})`, { - sort(a, z) { - return parseInt(a.value) - parseInt(z.value) - }, - }) - matchVariant('testmax', (value) => `@media (max-width: ${value})`, { - sort(a, z) { - return parseInt(z.value) - parseInt(a.value) - }, - }) - }, - ], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - @media (max-width: 400px) { - @media (min-width: 100px) { - .testmax-\[400px\]\:testmin-\[100px\]\:underline { - text-decoration-line: underline; +it('should guarantee that we are not passing values from other variants to the wrong function', () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('testmin', (value) => `@media (min-width: ${value})`, { + sort(a, z) { + let lookup = ['100px', '200px'] + if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) { + throw new Error('We are seeing values that should not be there!') } - } - @media (min-width: 200px) { - .testmax-\[400px\]\:testmin-\[200px\]\:underline { - text-decoration-line: underline; + return lookup.indexOf(a.value) - lookup.indexOf(z.value) + }, + }) + matchVariant('testmax', (value) => `@media (max-width: ${value})`, { + sort(a, z) { + let lookup = ['300px', '400px'] + if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) { + throw new Error('We are seeing values that should not be there!') } + return lookup.indexOf(z.value) - lookup.indexOf(a.value) + }, + }) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + @media (min-width: 100px) { + @media (max-width: 400px) { + .testmin-\[100px\]\:testmax-\[400px\]\:underline { + text-decoration-line: underline; } } @media (max-width: 300px) { - @media (min-width: 100px) { - .testmax-\[300px\]\:testmin-\[100px\]\:underline { - text-decoration-line: underline; - } - } - @media (min-width: 200px) { - .testmax-\[300px\]\:testmin-\[200px\]\:underline { - text-decoration-line: underline; - } + .testmin-\[100px\]\:testmax-\[300px\]\:underline { + text-decoration-line: underline; } } - `) - }) - }) - - it('should guarantee that we are not passing values from other variants to the wrong function', () => { - let config = { - content: [ - { - raw: html` -
-
-
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - ({ matchVariant }) => { - matchVariant('testmin', (value) => `@media (min-width: ${value})`, { - sort(a, z) { - let lookup = ['100px', '200px'] - if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) { - throw new Error('We are seeing values that should not be there!') - } - return lookup.indexOf(a.value) - lookup.indexOf(z.value) - }, - }) - matchVariant('testmax', (value) => `@media (max-width: ${value})`, { - sort(a, z) { - let lookup = ['300px', '400px'] - if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) { - throw new Error('We are seeing values that should not be there!') - } - return lookup.indexOf(z.value) - lookup.indexOf(a.value) - }, - }) - }, - ], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - @media (min-width: 100px) { - @media (max-width: 400px) { - .testmin-\[100px\]\:testmax-\[400px\]\:underline { - text-decoration-line: underline; - } - } + } - @media (max-width: 300px) { - .testmin-\[100px\]\:testmax-\[300px\]\:underline { - text-decoration-line: underline; - } + @media (min-width: 200px) { + @media (max-width: 400px) { + .testmin-\[200px\]\:testmax-\[400px\]\:underline { + text-decoration-line: underline; } } - @media (min-width: 200px) { - @media (max-width: 400px) { - .testmin-\[200px\]\:testmax-\[400px\]\:underline { - text-decoration-line: underline; - } - } - - @media (max-width: 300px) { - .testmin-\[200px\]\:testmax-\[300px\]\:underline { - text-decoration-line: underline; - } + @media (max-width: 300px) { + .testmin-\[200px\]\:testmax-\[300px\]\:underline { + text-decoration-line: underline; } } - `) - }) + } + `) }) +}) - it('should default to the DEFAULT value for variants', () => { - let config = { - content: [ - { - raw: html` -
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - ({ matchVariant }) => { - matchVariant('foo', (value) => `.foo${value} &`, { - values: { - DEFAULT: '.bar', - }, - }) - }, - ], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .foo.bar .foo\:underline { - text-decoration-line: underline; - } - `) - }) +it('should default to the DEFAULT value for variants', () => { + let config = { + content: [ + { + raw: html` +
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('foo', (value) => `.foo${value} &`, { + values: { + DEFAULT: '.bar', + }, + }) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo.bar .foo\:underline { + text-decoration-line: underline; + } + `) }) +}) - it('should not generate anything if the matchVariant does not have a DEFAULT value configured', () => { - let config = { - content: [ - { - raw: html` -
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - ({ matchVariant }) => { - matchVariant('foo', (value) => `.foo${value} &`) - }, - ], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css``) - }) +it('should not generate anything if the matchVariant does not have a DEFAULT value configured', () => { + let config = { + content: [ + { + raw: html` +
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('foo', (value) => `.foo${value} &`) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css``) }) +}) - it('should be possible to use `null` as a DEFAULT value', () => { - let config = { - content: [ - { - raw: html` -
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - ({ matchVariant }) => { - matchVariant('foo', (value) => `.foo${value === null ? '-good' : '-bad'} &`, { - values: { DEFAULT: null }, - }) - }, - ], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .foo-good .foo\:underline { - text-decoration-line: underline; - } - `) - }) +it('should be possible to use `null` as a DEFAULT value', () => { + let config = { + content: [ + { + raw: html` +
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('foo', (value) => `.foo${value === null ? '-good' : '-bad'} &`, { + values: { DEFAULT: null }, + }) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo-good .foo\:underline { + text-decoration-line: underline; + } + `) }) +}) - it('should be possible to use `undefined` as a DEFAULT value', () => { - let config = { - content: [ - { - raw: html` -
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - ({ matchVariant }) => { - matchVariant('foo', (value) => `.foo${value === undefined ? '-good' : '-bad'} &`, { - values: { DEFAULT: undefined }, - }) - }, - ], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .foo-good .foo\:underline { - text-decoration-line: underline; - } - `) - }) +it('should be possible to use `undefined` as a DEFAULT value', () => { + let config = { + content: [ + { + raw: html` +
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('foo', (value) => `.foo${value === undefined ? '-good' : '-bad'} &`, { + values: { DEFAULT: undefined }, + }) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo-good .foo\:underline { + text-decoration-line: underline; + } + `) }) +}) - it('should be possible to use `undefined` as a DEFAULT value', () => { - let config = { - content: [ - { - raw: html` -
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - ({ matchVariant }) => { - matchVariant('foo', (value) => `.foo${value === undefined ? '-good' : '-bad'} &`, { - values: { DEFAULT: undefined }, - }) - }, - ], - } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .foo-good .foo\:underline { - text-decoration-line: underline; - } - `) - }) +it('should be possible to use `undefined` as a DEFAULT value', () => { + let config = { + content: [ + { + raw: html` +
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + ({ matchVariant }) => { + matchVariant('foo', (value) => `.foo${value === undefined ? '-good' : '-bad'} &`, { + values: { DEFAULT: undefined }, + }) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo-good .foo\:underline { + text-decoration-line: underline; + } + `) }) +}) - it('should not break things', () => { - let config = {} +it('should not break things', () => { + let config = {} - let context = createContext(resolveConfig(config)) - let [[, fn]] = context.variantMap.get('group') + let context = createContext(resolveConfig(config)) + let [[, fn]] = context.variantMap.get('group') - let format + let format - expect( - fn({ - format(input) { - format = input - }, - }) - ).toBe(undefined) + expect( + fn({ + format(input) { + format = input + }, + }) + ).toBe(undefined) - expect(format).toBe(':merge(.group) &') - }) + expect(format).toBe(':merge(.group) &') }) diff --git a/tests/min-max-screen-variants.test.js b/tests/min-max-screen-variants.test.js index a86c6f6cd201..beab47ac0999 100644 --- a/tests/min-max-screen-variants.test.js +++ b/tests/min-max-screen-variants.test.js @@ -1,656 +1,642 @@ -import { crosscheck, run, html, css } from './util/run' - -crosscheck(() => { - let defaultScreens = { - sm: '640px', - md: '768px', - lg: '1024px', - xl: '1280px', - '2xl': '1536px', - } - - it('sorts min and max correctly relative to screens and each other', async () => { - let config = { - content: [ - { - raw: html` -
- `, - }, - ], - corePlugins: { preflight: false }, - theme: { - screens: defaultScreens, +import { run, html, css } from './util/run' + +let defaultScreens = { + sm: '640px', + md: '768px', + lg: '1024px', + xl: '1280px', + '2xl': '1536px', +} + +it('sorts min and max correctly relative to screens and each other', async () => { + let config = { + content: [ + { + raw: html` +
+ `, }, - } + ], + corePlugins: { preflight: false }, + theme: { + screens: defaultScreens, + }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .font-bold { + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .font-bold { + font-weight: 700; + } + @media (max-width: 800px) { + .max-\[800px\]\:font-bold { font-weight: 700; } - @media (max-width: 800px) { - .max-\[800px\]\:font-bold { - font-weight: 700; - } - } - @media (max-width: 700px) { - .max-\[700px\]\:font-bold { - font-weight: 700; - } - } - @media (min-width: 640px) { - .sm\:font-bold { - font-weight: 700; - } - } - @media (min-width: 700px) { - .min-\[700px\]\:font-bold { - font-weight: 700; - } - } - @media (min-width: 768px) { - .md\:font-bold { - font-weight: 700; - } + } + @media (max-width: 700px) { + .max-\[700px\]\:font-bold { + font-weight: 700; } - @media (min-width: 800px) { - .min-\[800px\]\:font-bold { - font-weight: 700; - } + } + @media (min-width: 640px) { + .sm\:font-bold { + font-weight: 700; } - `) - }) - }) - - it('works when using min variants screens config is empty and variants all use the same unit', async () => { - let config = { - content: [ - { - raw: html` -
- `, - }, - ], - corePlugins: { preflight: false }, - theme: { - screens: {}, - }, - } - - let input = css` - @tailwind utilities; - ` - - let result = await run(input, config) - - expect(result.css).toMatchFormattedCss(css` - .font-bold { - font-weight: 700; } @media (min-width: 700px) { .min-\[700px\]\:font-bold { font-weight: 700; } } + @media (min-width: 768px) { + .md\:font-bold { + font-weight: 700; + } + } @media (min-width: 800px) { .min-\[800px\]\:font-bold { font-weight: 700; } } `) - - expect().not.toHaveBeenWarned() }) +}) - it('works when using max variants screens config is empty and variants all use the same unit', () => { - let config = { - content: [ - { - raw: html` -
- `, - }, - ], - corePlugins: { preflight: false }, - theme: { - screens: {}, +it('works when using min variants screens config is empty and variants all use the same unit', async () => { + let config = { + content: [ + { + raw: html` +
+ `, }, - } + ], + corePlugins: { preflight: false }, + theme: { + screens: {}, + }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .font-bold { - font-weight: 700; - } - @media (max-width: 800px) { - .max-\[800px\]\:font-bold { - font-weight: 700; - } - } - @media (max-width: 700px) { - .max-\[700px\]\:font-bold { - font-weight: 700; - } - } - `) - }) - }) + let result = await run(input, config) - it('converts simple min-width screens for max variant', () => { - let config = { - content: [ - { - raw: html` -
- `, - }, - ], - corePlugins: { preflight: false }, - theme: { - screens: defaultScreens, - }, + expect(result.css).toMatchFormattedCss(css` + .font-bold { + font-weight: 700; } - - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .font-bold { - font-weight: 700; - } - @media not all and (min-width: 1024px) { - .max-lg\:font-bold { - font-weight: 700; - } - } - @media (max-width: 700px) { - .max-\[700px\]\:font-bold { - font-weight: 700; - } - } - @media not all and (min-width: 640px) { - .max-sm\:font-bold { - font-weight: 700; - } - } - @media (max-width: 300px) { - .max-\[300px\]\:font-bold { - font-weight: 700; - } - } - @media (min-width: 640px) { - .sm\:font-bold { - font-weight: 700; - } - } - @media (min-width: 768px) { - .md\:font-bold { - font-weight: 700; - } - } - `) - }) - }) - - it('does not have keyed screens for min variant', () => { - let config = { - content: [ - { - raw: html` -
- `, - }, - ], - corePlugins: { preflight: false }, - theme: { - screens: defaultScreens, - }, + @media (min-width: 700px) { + .min-\[700px\]\:font-bold { + font-weight: 700; + } } + @media (min-width: 800px) { + .min-\[800px\]\:font-bold { + font-weight: 700; + } + } + `) - let input = css` - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .font-bold { - font-weight: 700; - } - @media (min-width: 300px) { - .min-\[300px\]\:font-bold { - font-weight: 700; - } - } - @media (min-width: 640px) { - .sm\:font-bold { - font-weight: 700; - } - } - @media (min-width: 700px) { - .min-\[700px\]\:font-bold { - font-weight: 700; - } - } - @media (min-width: 768px) { - .md\:font-bold { - font-weight: 700; - } - } - `) - }) - }) + expect().not.toHaveBeenWarned() +}) - it('warns when using min variants with complex screen configs', async () => { - let config = { - content: [ - { - raw: html` -
- `, - }, - ], - corePlugins: { preflight: false }, - theme: { - screens: { - sm: '640px', - md: '768px', - lg: '1024px', - xl: '1280px', - '2xl': '1536px', - - // Any presence of an object makes it complex - yodawg: { min: '700px' }, - }, +it('works when using max variants screens config is empty and variants all use the same unit', () => { + let config = { + content: [ + { + raw: html` +
+ `, }, - } - - let input = css` - @tailwind utilities; - ` + ], + corePlugins: { preflight: false }, + theme: { + screens: {}, + }, + } - let result = await run(input, config) + let input = css` + @tailwind utilities; + ` + return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` .font-bold { font-weight: 700; } - @media (min-width: 640px) { - .sm\:font-bold { + @media (max-width: 800px) { + .max-\[800px\]\:font-bold { font-weight: 700; } } - @media (min-width: 768px) { - .md\:font-bold { + @media (max-width: 700px) { + .max-\[700px\]\:font-bold { font-weight: 700; } } `) - - expect().toHaveBeenWarnedWith(['complex-screen-config']) }) +}) - it('warns when using min variants with simple configs containing mixed units', async () => { - let config = { - content: [ - { - raw: html` -
- `, - }, - ], - corePlugins: { preflight: false }, - theme: { - screens: { - sm: '640px', - md: '48rem', - lg: '1024px', - xl: '1280px', - '2xl': '1536px', - }, +it('converts simple min-width screens for max variant', () => { + let config = { + content: [ + { + raw: html` +
+ `, }, - } - - let input = css` - @tailwind utilities; - ` + ], + corePlugins: { preflight: false }, + theme: { + screens: defaultScreens, + }, + } - let result = await run(input, config) + let input = css` + @tailwind utilities; + ` + return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` .font-bold { font-weight: 700; } + @media not all and (min-width: 1024px) { + .max-lg\:font-bold { + font-weight: 700; + } + } + @media (max-width: 700px) { + .max-\[700px\]\:font-bold { + font-weight: 700; + } + } + @media not all and (min-width: 640px) { + .max-sm\:font-bold { + font-weight: 700; + } + } + @media (max-width: 300px) { + .max-\[300px\]\:font-bold { + font-weight: 700; + } + } @media (min-width: 640px) { .sm\:font-bold { font-weight: 700; } } - @media (min-width: 48rem) { + @media (min-width: 768px) { .md\:font-bold { font-weight: 700; } } `) - - expect().toHaveBeenWarnedWith(['mixed-screen-units']) }) +}) - it('warns when using min variants with mixed units (with screens config)', async () => { - let config = { - content: [ - { - raw: html` -
- `, - }, - ], - corePlugins: { preflight: false }, - theme: { - screens: { - sm: '640px', - md: '768px', - lg: '1024px', - xl: '1280px', - '2xl': '1536px', - }, +it('does not have keyed screens for min variant', () => { + let config = { + content: [ + { + raw: html` +
+ `, }, - } - - let input = css` - @tailwind utilities; - ` + ], + corePlugins: { preflight: false }, + theme: { + screens: defaultScreens, + }, + } - let result = await run(input, config) + let input = css` + @tailwind utilities; + ` + return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` .font-bold { font-weight: 700; } + @media (min-width: 300px) { + .min-\[300px\]\:font-bold { + font-weight: 700; + } + } @media (min-width: 640px) { .sm\:font-bold { font-weight: 700; } } + @media (min-width: 700px) { + .min-\[700px\]\:font-bold { + font-weight: 700; + } + } @media (min-width: 768px) { .md\:font-bold { font-weight: 700; } } `) - - expect().toHaveBeenWarnedWith(['minmax-have-mixed-units']) }) +}) - it('warns when using min variants with mixed units (with no screens config)', async () => { - let config = { - content: [ - { - raw: html` -
- `, - }, - ], - corePlugins: { preflight: false }, - theme: { - screens: {}, +it('warns when using min variants with complex screen configs', async () => { + let config = { + content: [ + { + raw: html`
`, }, - } + ], + corePlugins: { preflight: false }, + theme: { + screens: { + sm: '640px', + md: '768px', + lg: '1024px', + xl: '1280px', + '2xl': '1536px', + + // Any presence of an object makes it complex + yodawg: { min: '700px' }, + }, + }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - let result = await run(input, config) + let result = await run(input, config) - expect(result.css).toMatchFormattedCss(css` - .font-bold { + expect(result.css).toMatchFormattedCss(css` + .font-bold { + font-weight: 700; + } + @media (min-width: 640px) { + .sm\:font-bold { font-weight: 700; } - @media (min-width: 700px) { - .min-\[700px\]\:font-bold { - font-weight: 700; - } + } + @media (min-width: 768px) { + .md\:font-bold { + font-weight: 700; } - `) + } + `) - expect().toHaveBeenWarnedWith(['minmax-have-mixed-units']) - }) + expect().toHaveBeenWarnedWith(['complex-screen-config']) +}) - it('warns when using max variants with complex screen configs', async () => { - let config = { - content: [ - { - raw: html` -
- `, - }, - ], - corePlugins: { preflight: false }, - theme: { - screens: { - sm: '640px', - md: '768px', - lg: '1024px', - xl: '1280px', - '2xl': '1536px', - - // Any presence of an object makes it complex - yodawg: { min: '700px' }, - }, +it('warns when using min variants with simple configs containing mixed units', async () => { + let config = { + content: [ + { + raw: html`
`, }, - } + ], + corePlugins: { preflight: false }, + theme: { + screens: { + sm: '640px', + md: '48rem', + lg: '1024px', + xl: '1280px', + '2xl': '1536px', + }, + }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - let result = await run(input, config) + let result = await run(input, config) - expect(result.css).toMatchFormattedCss(css` - .font-bold { + expect(result.css).toMatchFormattedCss(css` + .font-bold { + font-weight: 700; + } + @media (min-width: 640px) { + .sm\:font-bold { font-weight: 700; } - @media (min-width: 640px) { - .sm\:font-bold { - font-weight: 700; - } - } - @media (min-width: 768px) { - .md\:font-bold { - font-weight: 700; - } + } + @media (min-width: 48rem) { + .md\:font-bold { + font-weight: 700; } - `) + } + `) - expect().toHaveBeenWarnedWith(['complex-screen-config']) - }) + expect().toHaveBeenWarnedWith(['mixed-screen-units']) +}) - it('warns when using max variants with simple configs containing mixed units', async () => { - let config = { - content: [ - { - raw: html` -
- `, - }, - ], - corePlugins: { preflight: false }, - theme: { - screens: { - sm: '640px', - md: '48rem', - lg: '1024px', - xl: '1280px', - '2xl': '1536px', - }, +it('warns when using min variants with mixed units (with screens config)', async () => { + let config = { + content: [ + { + raw: html`
`, }, - } + ], + corePlugins: { preflight: false }, + theme: { + screens: { + sm: '640px', + md: '768px', + lg: '1024px', + xl: '1280px', + '2xl': '1536px', + }, + }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - let result = await run(input, config) + let result = await run(input, config) - expect(result.css).toMatchFormattedCss(css` - .font-bold { + expect(result.css).toMatchFormattedCss(css` + .font-bold { + font-weight: 700; + } + @media (min-width: 640px) { + .sm\:font-bold { font-weight: 700; } - @media (min-width: 640px) { - .sm\:font-bold { - font-weight: 700; - } - } - @media (min-width: 48rem) { - .md\:font-bold { - font-weight: 700; - } + } + @media (min-width: 768px) { + .md\:font-bold { + font-weight: 700; } - `) + } + `) - expect().toHaveBeenWarnedWith(['mixed-screen-units']) - }) + expect().toHaveBeenWarnedWith(['minmax-have-mixed-units']) +}) - it('warns when using max variants with mixed units (with screens config)', async () => { - let config = { - content: [ - { - raw: html` -
- `, - }, - ], - corePlugins: { preflight: false }, - theme: { - screens: { - sm: '640px', - md: '768px', - lg: '1024px', - xl: '1280px', - '2xl': '1536px', - }, +it('warns when using min variants with mixed units (with no screens config)', async () => { + let config = { + content: [ + { + raw: html` +
+ `, }, - } + ], + corePlugins: { preflight: false }, + theme: { + screens: {}, + }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - let result = await run(input, config) + let result = await run(input, config) - expect(result.css).toMatchFormattedCss(css` - .font-bold { + expect(result.css).toMatchFormattedCss(css` + .font-bold { + font-weight: 700; + } + @media (min-width: 700px) { + .min-\[700px\]\:font-bold { font-weight: 700; } - @media (min-width: 640px) { - .sm\:font-bold { - font-weight: 700; - } + } + `) + + expect().toHaveBeenWarnedWith(['minmax-have-mixed-units']) +}) + +it('warns when using max variants with complex screen configs', async () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + theme: { + screens: { + sm: '640px', + md: '768px', + lg: '1024px', + xl: '1280px', + '2xl': '1536px', + + // Any presence of an object makes it complex + yodawg: { min: '700px' }, + }, + }, + } + + let input = css` + @tailwind utilities; + ` + + let result = await run(input, config) + + expect(result.css).toMatchFormattedCss(css` + .font-bold { + font-weight: 700; + } + @media (min-width: 640px) { + .sm\:font-bold { + font-weight: 700; } - @media (min-width: 768px) { - .md\:font-bold { - font-weight: 700; - } + } + @media (min-width: 768px) { + .md\:font-bold { + font-weight: 700; } - `) + } + `) - expect().toHaveBeenWarnedWith(['minmax-have-mixed-units']) - }) + expect().toHaveBeenWarnedWith(['complex-screen-config']) +}) - it('warns when using max variants with mixed units (with no screens config)', async () => { - let config = { - content: [ - { - raw: html` -
- `, - }, - ], - corePlugins: { preflight: false }, - theme: { - screens: {}, +it('warns when using max variants with simple configs containing mixed units', async () => { + let config = { + content: [ + { + raw: html`
`, }, - } + ], + corePlugins: { preflight: false }, + theme: { + screens: { + sm: '640px', + md: '48rem', + lg: '1024px', + xl: '1280px', + '2xl': '1536px', + }, + }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - let result = await run(input, config) + let result = await run(input, config) - expect(result.css).toMatchFormattedCss(css` - .font-bold { + expect(result.css).toMatchFormattedCss(css` + .font-bold { + font-weight: 700; + } + @media (min-width: 640px) { + .sm\:font-bold { font-weight: 700; } - @media (max-width: 700px) { - .max-\[700px\]\:font-bold { - font-weight: 700; - } + } + @media (min-width: 48rem) { + .md\:font-bold { + font-weight: 700; } - `) + } + `) - expect().toHaveBeenWarnedWith(['minmax-have-mixed-units']) - }) + expect().toHaveBeenWarnedWith(['mixed-screen-units']) +}) - it('warns when using min and max variants with mixed units (with no screens config)', async () => { - let config = { - content: [ - { - raw: html` -
- `, - }, - ], - corePlugins: { preflight: false }, - theme: { - screens: {}, +it('warns when using max variants with mixed units (with screens config)', async () => { + let config = { + content: [ + { + raw: html`
`, }, + ], + corePlugins: { preflight: false }, + theme: { + screens: { + sm: '640px', + md: '768px', + lg: '1024px', + xl: '1280px', + '2xl': '1536px', + }, + }, + } + + let input = css` + @tailwind utilities; + ` + + let result = await run(input, config) + + expect(result.css).toMatchFormattedCss(css` + .font-bold { + font-weight: 700; + } + @media (min-width: 640px) { + .sm\:font-bold { + font-weight: 700; + } } + @media (min-width: 768px) { + .md\:font-bold { + font-weight: 700; + } + } + `) + + expect().toHaveBeenWarnedWith(['minmax-have-mixed-units']) +}) - let input = css` - @tailwind utilities; - ` +it('warns when using max variants with mixed units (with no screens config)', async () => { + let config = { + content: [ + { + raw: html` +
+ `, + }, + ], + corePlugins: { preflight: false }, + theme: { + screens: {}, + }, + } - let result = await run(input, config) + let input = css` + @tailwind utilities; + ` - expect(result.css).toMatchFormattedCss(css` - .font-bold { + let result = await run(input, config) + + expect(result.css).toMatchFormattedCss(css` + .font-bold { + font-weight: 700; + } + @media (max-width: 700px) { + .max-\[700px\]\:font-bold { font-weight: 700; } - @media (max-width: 700rem) { - .max-\[700rem\]\:font-bold { - font-weight: 700; - } + } + `) + + expect().toHaveBeenWarnedWith(['minmax-have-mixed-units']) +}) + +it('warns when using min and max variants with mixed units (with no screens config)', async () => { + let config = { + content: [ + { + raw: html` +
+ `, + }, + ], + corePlugins: { preflight: false }, + theme: { + screens: {}, + }, + } + + let input = css` + @tailwind utilities; + ` + + let result = await run(input, config) + + expect(result.css).toMatchFormattedCss(css` + .font-bold { + font-weight: 700; + } + @media (max-width: 700rem) { + .max-\[700rem\]\:font-bold { + font-weight: 700; } - `) + } + `) - expect().toHaveBeenWarnedWith(['minmax-have-mixed-units']) - }) + expect().toHaveBeenWarnedWith(['minmax-have-mixed-units']) }) diff --git a/tests/minimum-impact-selector.test.js b/tests/minimum-impact-selector.test.js index ed2fad9147f3..d8292e4679dd 100644 --- a/tests/minimum-impact-selector.test.js +++ b/tests/minimum-impact-selector.test.js @@ -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) }) diff --git a/tests/modify-selectors.test.js b/tests/modify-selectors.test.js index 2efbe206a11f..364b84a7ec7e 100644 --- a/tests/modify-selectors.test.js +++ b/tests/modify-selectors.test.js @@ -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` -
-
-
-
-
-
-

Lorem ipsum dolor sit amet...

-
-
-

Lorem ipsum dolor sit amet...

-
-
-

Lorem ipsum dolor sit amet...

-
-
-

Lorem ipsum dolor sit amet...

-
- `, - }, - ], - 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` +
+
+
+
+
+
+

Lorem ipsum dolor sit amet...

+
+
+

Lorem ipsum dolor sit amet...

+
+
+

Lorem ipsum dolor sit amet...

+
+
+

Lorem ipsum dolor sit amet...

+
+ `, + }, + ], + 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; + let input = css` + @tailwind components; + @tailwind utilities; - @layer components { - .markdown > p { - margin-top: 12px; - } + @layer components { + .markdown > p { + margin-top: 12px; } - ` + } + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .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; } - .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; - } - } - `) - }) + } + `) }) }) diff --git a/tests/mutable.test.js b/tests/mutable.test.js index 45419380f143..5c3f9a4873e0 100644 --- a/tests/mutable.test.js +++ b/tests/mutable.test.js @@ -1,54 +1,52 @@ import path from 'path' import postcss from 'postcss' -import { crosscheck, 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")' - }) - - return rule - }) - } +import { run, html, css } from './util/run' + +function pluginThatMutatesRules() { + return (root) => { + root.walkRules((rule) => { + rule.nodes + .filter((node) => node.prop === 'background-image') + .forEach((node) => { + node.value = 'url("./bar.png")' + }) + + return rule + }) } - - test('plugins mutating rules after tailwind doesnt break it', async () => { - let config = { - content: [{ raw: html`
` }], - theme: { - backgroundImage: { - foo: 'url("./foo.png")', - }, +} + +test('plugins mutating rules after tailwind doesnt break it', async () => { + let config = { + content: [{ raw: html`
` }], + theme: { + backgroundImage: { + foo: 'url("./foo.png")', }, - plugins: [], - } - - 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) - - // 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), - }) + }, + plugins: [], + } - // Verify subsequent runs don't produce mutated rules - let secondRun = await run('@tailwind utilities', config) - checkResult(secondRun) + 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) + + // 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) }) diff --git a/tests/negateValue.test.js b/tests/negateValue.test.js index 052132f914ab..c31741d48876 100644 --- a/tests/negateValue.test.js +++ b/tests/negateValue.test.js @@ -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() }) diff --git a/tests/negated-content.test.js b/tests/negated-content.test.js index 66e23194655f..6d46e3a8d231 100644 --- a/tests/negated-content.test.js +++ b/tests/negated-content.test.js @@ -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; + } + `) }) }) diff --git a/tests/negative-prefix.test.js b/tests/negative-prefix.test.js index d352c1d63e0e..dcecd3c5be3a 100644 --- a/tests/negative-prefix.test.js +++ b/tests/negative-prefix.test.js @@ -1,366 +1,362 @@ -import { crosscheck, run, html, css } from './util/run' - -crosscheck(() => { - test('using a negative prefix with a negative scale value', () => { - let config = { - content: [{ raw: html`
` }], - theme: { - margin: { - 2: '8px', - '-2': '-4px', - }, +import { run, html, css } from './util/run' + +test('using a negative prefix with a negative scale value', () => { + let config = { + content: [{ raw: html`
` }], + 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; - } - `) - }) + }, + } + + 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`
` }], - theme: { - opacity: { - '-50': '0.5', - }, +test('using a negative scale value with a plugin that does not support dynamic negative values', () => { + let config = { + content: [{ raw: html`
` }], + theme: { + opacity: { + '-50': '0.5', }, - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchCss(css` - .-opacity-50 { - opacity: 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`
` }], - theme: { - margin: { - 5: '20px', - }, +test('using a negative prefix without a negative scale value', () => { + let config = { + content: [{ raw: html`
` }], + 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; - } - `) - }) + }, + } + + 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`
` }], - theme: { - margin: { - '-[10px]': '55px', - }, +test('being an asshole', () => { + let config = { + content: [{ raw: html`
` }], + theme: { + margin: { + '-[10px]': '55px', }, - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchCss(css` - .-mt-\[10px\] { - margin-top: 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`
` }], - theme: { - margin: { - '[10px]': '55px', - }, +test('being a real asshole', () => { + let config = { + content: [{ raw: html`
` }], + theme: { + margin: { + '[10px]': '55px', }, - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchCss(css` - .-mt-\[10px\] { - margin-top: -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`
` }], - theme: { - margin: { - 5: 'var(--sizing-5)', - }, +test('a value that includes a variable', () => { + let config = { + content: [{ raw: html`
` }], + 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); - } - `) - }) + }, + } + + 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`
` }], - theme: { - margin: { - 5: 'calc(52px * -3)', - }, +test('a value that includes a calc', () => { + let config = { + content: [{ raw: html`
` }], + 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; - } - `) - }) + }, + } + + 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`
` }, - ], - theme: { - margin: { - min: 'min(100vmin, 3rem)', - max: 'max(100vmax, 3rem)', - clamp: 'clamp(1rem, 100vh, 3rem)', - }, +test('a value that includes min/max/clamp functions', () => { + let config = { + content: [{ raw: html`
` }], + 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); - } - `) - }) + }, + } + + 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`
` }], - theme: { - margin: { - auto: 'auto', - }, +test('a keyword value', () => { + let config = { + content: [{ raw: html`
` }], + theme: { + margin: { + auto: 'auto', }, - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchCss(css` - .mt-auto { - margin-top: 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`
` }], - theme: { - margin: { - 0: '0', - }, +test('a zero value', () => { + let config = { + content: [{ raw: html`
` }], + theme: { + margin: { + 0: '0', }, - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchCss(css` - .-mt-0, - .mt-0 { - margin-top: 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`
` }], - theme: { - colors: { - red: 'red', - }, +test('a color', () => { + let config = { + content: [{ raw: html`
` }], + theme: { + colors: { + red: 'red', }, - } + }, + } - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchCss(css``) - }) + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchCss(css``) }) +}) - test('arbitrary values', () => { - let config = { - content: [{ raw: html`
` }], - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchCss(css` - .-mt-\[10px\] { - margin-top: -10px; - } - `) - }) +test('arbitrary values', () => { + let config = { + content: [{ raw: html`
` }], + } + + 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`
` }], - theme: { - margin: { - weird: '-15px', - }, +test('negating a negative scale value', () => { + let config = { + content: [{ raw: html`
` }], + theme: { + margin: { + weird: '-15px', }, - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchCss(css` - .-mt-weird { - margin-top: 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`
` }], - theme: { - margin: { - DEFAULT: '15px', - }, +test('negating a default value', () => { + let config = { + content: [{ raw: html`
` }], + theme: { + margin: { + DEFAULT: '15px', }, - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchCss(css` - .-mt { - margin-top: -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`
` }], - theme: { - margin: { - DEFAULT: '8px', - '-DEFAULT': '-4px', - }, +test('using a negative prefix with a negative default scale value', () => { + let config = { + content: [{ raw: html`
` }], + 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; - } - `) - }) + }, + } + + 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`
` }], - theme: { - margin: { - DEFAULT: '15px', - }, +test('negating a default value with a configured prefix', () => { + let config = { + prefix: 'tw-', + content: [{ raw: html`
` }], + theme: { + margin: { + DEFAULT: '15px', }, - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchCss(css` - .tw--mt { - margin-top: -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`
` }], - } +test('arbitrary value keywords should be ignored', () => { + let config = { + content: [{ raw: html`
` }], + } - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchCss(css``) - }) + 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`
` }], - plugins: [ - ({ addUtilities }) => { - addUtilities({ - '.top-lg': { - top: '6rem', - }, - }) - }, - ], - } - - let result = await run('@tailwind utilities', config) - - 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`
` }], + plugins: [ + ({ addUtilities }) => { + addUtilities({ + '.top-lg': { + top: '6rem', + }, + }) + }, + ], + } + + let result = await run('@tailwind utilities', config) + + expect(result.css).toMatchCss(css``) }) diff --git a/tests/normalize-config.test.js b/tests/normalize-config.test.js index a0b7a59bb950..6700ddbb11be 100644 --- a/tests/normalize-config.test.js +++ b/tests/normalize-config.test.js @@ -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: [{ 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; - } - `) - }) +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; - } - `) - }) +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' - }, +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']) }, }, - theme: { - extends: {}, + extract: { + svelte(content) { + return content.replace('svelte', 'center').split(' ') + }, }, - variants: { - extends: {}, + 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; - } - `) - }) + 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}'], - } +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: {}, - }) + 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 +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}', + ], + } - // 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: {}, - }) + let normalizedConfig = normalizeConfig(resolveConfig(config)).content - expect().toHaveBeenWarnedWith(['invalid-glob-braces']) + // 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']) }) diff --git a/tests/normalize-data-types.test.js b/tests/normalize-data-types.test.js index 356798ed3d33..224203ffe14c 100644 --- a/tests/normalize-data-types.test.js +++ b/tests/normalize-data-types.test.js @@ -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 `` as the value', () => { diff --git a/tests/normalize-screens.test.js b/tests/normalize-screens.test.js index 464623f9f5fe..da31c50d30d7 100644 --- a/tests/normalize-screens.test.js +++ b/tests/normalize-screens.test.js @@ -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: '768px', not: false, values: [{ min: '768px', max: undefined }] }, + { name: '1200px', not: false, values: [{ min: '1200px', max: undefined }] }, + ]) +}) - 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 string values', () => { + let screens = { + a: '768px', + b: '1200px', + } - 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: '1200px', max: undefined }] }, + ]) +}) - 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 object values', () => { + let screens = { + a: { min: '768px' }, + b: { 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: [{ min: '768px', max: undefined }] }, + { name: 'b', not: false, values: [{ min: undefined, 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 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' }, - } +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: 'a', not: false, values: [{ min: '768px', max: undefined }] }, + { name: 'b', not: false, values: [{ min: undefined, max: '1200px' }] }, + ]) }) diff --git a/tests/opacity.test.js b/tests/opacity.test.js index b5a1a53cfee7..8f688210cead 100644 --- a/tests/opacity.test.js +++ b/tests/opacity.test.js @@ -1,1119 +1,1051 @@ -import { crosscheck, run, html, css } from './util/run' +import { run, html, css } from './util/run' -crosscheck(({ stable, oxide }) => { - test('opacity', () => { - let config = { - darkMode: 'selector', - content: [ - { - raw: html` -
-
-
-
-
- `, - }, - ], - corePlugins: { - backgroundOpacity: false, - borderOpacity: false, - divideOpacity: false, - placeholderOpacity: false, - textOpacity: false, +test('opacity', () => { + let config = { + darkMode: 'selector', + content: [ + { + raw: html` +
+
+
+
+
+ `, }, - } + ], + corePlugins: { + backgroundOpacity: false, + borderOpacity: false, + divideOpacity: false, + placeholderOpacity: false, + textOpacity: false, + }, + } - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .divide-black > :not([hidden]) ~ :not([hidden]), - .border-black { - border-color: #000; - } - .bg-black { - background-color: #000; - } - .text-black, - .placeholder-black::placeholder { - color: #000; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .divide-black > :not([hidden]) ~ :not([hidden]), + .border-black { + border-color: #000; + } + .bg-black { + background-color: #000; + } + .text-black, + .placeholder-black::placeholder { + color: #000; + } + `) }) +}) - test('colors defined as functions work when opacity plugins are disabled', () => { - let config = { - darkMode: 'selector', - content: [ - { - raw: html` -
-
-
-
-
- `, - }, - ], - theme: { - colors: { - primary: ({ opacityValue }) => - opacityValue === undefined - ? 'rgb(var(--color-primary))' - : `rgb(var(--color-primary) / ${opacityValue})`, - }, +test('colors defined as functions work when opacity plugins are disabled', () => { + let config = { + darkMode: 'selector', + content: [ + { + raw: html` +
+
+
+
+
+ `, }, - corePlugins: { - backgroundOpacity: false, - borderOpacity: false, - divideOpacity: false, - placeholderOpacity: false, - textOpacity: false, + ], + theme: { + colors: { + primary: ({ opacityValue }) => + opacityValue === undefined + ? 'rgb(var(--color-primary))' + : `rgb(var(--color-primary) / ${opacityValue})`, }, - } + }, + corePlugins: { + backgroundOpacity: false, + borderOpacity: false, + divideOpacity: false, + placeholderOpacity: false, + textOpacity: false, + }, + } - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .divide-primary > :not([hidden]) ~ :not([hidden]), - .border-primary { - border-color: rgb(var(--color-primary)); - } - .bg-primary { - background-color: rgb(var(--color-primary)); - } - .text-primary, - .placeholder-primary::placeholder { - color: rgb(var(--color-primary)); - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .divide-primary > :not([hidden]) ~ :not([hidden]), + .border-primary { + border-color: rgb(var(--color-primary)); + } + .bg-primary { + background-color: rgb(var(--color-primary)); + } + .text-primary, + .placeholder-primary::placeholder { + color: rgb(var(--color-primary)); + } + `) }) +}) - it('can use defining custom properties for colors (opacity plugins enabled)', () => { - let config = { - content: [ - { - raw: html` -
-
-
-
-
-
-
-
-
-
-
-
- `, - }, - ], - theme: { - colors: { - primary: 'rgb(var(--color-primary) / )', - }, +it('can use defining custom properties for colors (opacity plugins enabled)', () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+
+
+
+
+
+
+
+
+ `, }, - } + ], + theme: { + colors: { + primary: 'rgb(var(--color-primary) / )', + }, + }, + } - return run('@tailwind utilities', config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .divide-primary > :not([hidden]) ~ :not([hidden]) { - --tw-divide-opacity: 1; - border-color: rgb(var(--color-primary) / var(--tw-divide-opacity)); - } - .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) { - --tw-divide-opacity: 0.5; - } - .border-primary { - --tw-border-opacity: 1; - border-color: rgb(var(--color-primary) / var(--tw-border-opacity)); - } - .border-opacity-50 { - --tw-border-opacity: 0.5; - } - .bg-primary { - --tw-bg-opacity: 1; - background-color: rgb(var(--color-primary) / var(--tw-bg-opacity)); - } - .bg-opacity-50 { - --tw-bg-opacity: 0.5; - } - .text-primary { - --tw-text-opacity: 1; - color: rgb(var(--color-primary) / var(--tw-text-opacity)); - } - .text-opacity-50 { - --tw-text-opacity: 0.5; - } - .placeholder-primary::placeholder { - --tw-placeholder-opacity: 1; - color: rgb(var(--color-primary) / var(--tw-placeholder-opacity)); - } - .placeholder-opacity-50::placeholder { - --tw-placeholder-opacity: 0.5; - } - .ring-primary { - --tw-ring-opacity: 1; - --tw-ring-color: rgb(var(--color-primary) / var(--tw-ring-opacity)); - } - .ring-opacity-50 { - --tw-ring-opacity: 0.5; - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .divide-primary > :not([hidden]) ~ :not([hidden]), - .border-primary { - border-color: rgb(var(--color-primary) / 1); - } - .bg-primary { - background-color: rgb(var(--color-primary) / 1); - } - .text-primary, - .placeholder-primary::placeholder { - color: rgb(var(--color-primary) / 1); - } - .ring-primary { - --tw-ring-color: rgb(var(--color-primary) / 1); - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .divide-primary > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 1; + border-color: rgb(var(--color-primary) / var(--tw-divide-opacity)); + } + .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 0.5; + } + .border-primary { + --tw-border-opacity: 1; + border-color: rgb(var(--color-primary) / var(--tw-border-opacity)); + } + .border-opacity-50 { + --tw-border-opacity: 0.5; + } + .bg-primary { + --tw-bg-opacity: 1; + background-color: rgb(var(--color-primary) / var(--tw-bg-opacity)); + } + .bg-opacity-50 { + --tw-bg-opacity: 0.5; + } + .text-primary { + --tw-text-opacity: 1; + color: rgb(var(--color-primary) / var(--tw-text-opacity)); + } + .text-opacity-50 { + --tw-text-opacity: 0.5; + } + .placeholder-primary::placeholder { + --tw-placeholder-opacity: 1; + color: rgb(var(--color-primary) / var(--tw-placeholder-opacity)); + } + .placeholder-opacity-50::placeholder { + --tw-placeholder-opacity: 0.5; + } + .ring-primary { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(var(--color-primary) / var(--tw-ring-opacity)); + } + .ring-opacity-50 { + --tw-ring-opacity: 0.5; + } + `) }) +}) - it('can use rgb helper when defining custom properties for colors (opacity plugins disabled)', () => { - let config = { - content: [ - { - raw: html` -
-
-
-
-
-
-
-
-
-
-
-
- `, - }, - ], - theme: { - colors: { - primary: 'rgb(var(--color-primary) / )', - }, +it('can use rgb helper when defining custom properties for colors (opacity plugins disabled)', () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+
+
+
+
+
+
+
+
+ `, }, - corePlugins: { - backgroundOpacity: false, - borderOpacity: false, - divideOpacity: false, - placeholderOpacity: false, - textOpacity: false, - ringOpacity: false, + ], + theme: { + colors: { + primary: 'rgb(var(--color-primary) / )', }, - } + }, + corePlugins: { + backgroundOpacity: false, + borderOpacity: false, + divideOpacity: false, + placeholderOpacity: false, + textOpacity: false, + ringOpacity: false, + }, + } - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .divide-primary > :not([hidden]) ~ :not([hidden]) { - border-color: rgb(var(--color-primary) / 1); - } - .divide-primary\/50 > :not([hidden]) ~ :not([hidden]) { - border-color: rgb(var(--color-primary) / 0.5); - } - .border-primary { - border-color: rgb(var(--color-primary) / 1); - } - .border-primary\/50 { - border-color: rgb(var(--color-primary) / 0.5); - } - .bg-primary { - background-color: rgb(var(--color-primary) / 1); - } - .bg-primary\/50 { - background-color: rgb(var(--color-primary) / 0.5); - } - .text-primary { - color: rgb(var(--color-primary) / 1); - } - .text-primary\/50 { - color: rgb(var(--color-primary) / 0.5); - } - .placeholder-primary::placeholder { - color: rgb(var(--color-primary) / 1); - } - .placeholder-primary\/50::placeholder { - color: rgb(var(--color-primary) / 0.5); - } - .ring-primary { - --tw-ring-color: rgb(var(--color-primary) / 1); - } - .ring-primary\/50 { - --tw-ring-color: rgb(var(--color-primary) / 0.5); - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .divide-primary > :not([hidden]) ~ :not([hidden]) { + border-color: rgb(var(--color-primary) / 1); + } + .divide-primary\/50 > :not([hidden]) ~ :not([hidden]) { + border-color: rgb(var(--color-primary) / 0.5); + } + .border-primary { + border-color: rgb(var(--color-primary) / 1); + } + .border-primary\/50 { + border-color: rgb(var(--color-primary) / 0.5); + } + .bg-primary { + background-color: rgb(var(--color-primary) / 1); + } + .bg-primary\/50 { + background-color: rgb(var(--color-primary) / 0.5); + } + .text-primary { + color: rgb(var(--color-primary) / 1); + } + .text-primary\/50 { + color: rgb(var(--color-primary) / 0.5); + } + .placeholder-primary::placeholder { + color: rgb(var(--color-primary) / 1); + } + .placeholder-primary\/50::placeholder { + color: rgb(var(--color-primary) / 0.5); + } + .ring-primary { + --tw-ring-color: rgb(var(--color-primary) / 1); + } + .ring-primary\/50 { + --tw-ring-color: rgb(var(--color-primary) / 0.5); + } + `) }) +}) - it('can use hsl helper when defining custom properties for colors (opacity plugins enabled)', () => { - let config = { - content: [ - { - raw: html` -
-
-
-
-
-
-
-
-
-
-
-
- `, - }, - ], - theme: { - colors: { - primary: 'hsl(var(--color-primary) / )', - }, +it('can use hsl helper when defining custom properties for colors (opacity plugins enabled)', () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+
+
+
+
+
+
+
+
+ `, }, - } + ], + theme: { + colors: { + primary: 'hsl(var(--color-primary) / )', + }, + }, + } - return run('@tailwind utilities', config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .divide-primary > :not([hidden]) ~ :not([hidden]) { - --tw-divide-opacity: 1; - border-color: hsl(var(--color-primary) / var(--tw-divide-opacity)); - } - .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) { - --tw-divide-opacity: 0.5; - } - .border-primary { - --tw-border-opacity: 1; - border-color: hsl(var(--color-primary) / var(--tw-border-opacity)); - } - .border-opacity-50 { - --tw-border-opacity: 0.5; - } - .bg-primary { - --tw-bg-opacity: 1; - background-color: hsl(var(--color-primary) / var(--tw-bg-opacity)); - } - .bg-opacity-50 { - --tw-bg-opacity: 0.5; - } - .text-primary { - --tw-text-opacity: 1; - color: hsl(var(--color-primary) / var(--tw-text-opacity)); - } - .text-opacity-50 { - --tw-text-opacity: 0.5; - } - .placeholder-primary::placeholder { - --tw-placeholder-opacity: 1; - color: hsl(var(--color-primary) / var(--tw-placeholder-opacity)); - } - .placeholder-opacity-50::placeholder { - --tw-placeholder-opacity: 0.5; - } - .ring-primary { - --tw-ring-opacity: 1; - --tw-ring-color: hsl(var(--color-primary) / var(--tw-ring-opacity)); - } - .ring-opacity-50 { - --tw-ring-opacity: 0.5; - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .divide-primary > :not([hidden]) ~ :not([hidden]), - .border-primary { - border-color: hsl(var(--color-primary) / 1); - } - .bg-primary { - background-color: hsl(var(--color-primary) / 1); - } - .text-primary, - .placeholder-primary::placeholder { - color: hsl(var(--color-primary) / 1); - } - .ring-primary { - --tw-ring-color: hsl(var(--color-primary) / 1); - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .divide-primary > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 1; + border-color: hsl(var(--color-primary) / var(--tw-divide-opacity)); + } + .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 0.5; + } + .border-primary { + --tw-border-opacity: 1; + border-color: hsl(var(--color-primary) / var(--tw-border-opacity)); + } + .border-opacity-50 { + --tw-border-opacity: 0.5; + } + .bg-primary { + --tw-bg-opacity: 1; + background-color: hsl(var(--color-primary) / var(--tw-bg-opacity)); + } + .bg-opacity-50 { + --tw-bg-opacity: 0.5; + } + .text-primary { + --tw-text-opacity: 1; + color: hsl(var(--color-primary) / var(--tw-text-opacity)); + } + .text-opacity-50 { + --tw-text-opacity: 0.5; + } + .placeholder-primary::placeholder { + --tw-placeholder-opacity: 1; + color: hsl(var(--color-primary) / var(--tw-placeholder-opacity)); + } + .placeholder-opacity-50::placeholder { + --tw-placeholder-opacity: 0.5; + } + .ring-primary { + --tw-ring-opacity: 1; + --tw-ring-color: hsl(var(--color-primary) / var(--tw-ring-opacity)); + } + .ring-opacity-50 { + --tw-ring-opacity: 0.5; + } + `) }) +}) - it('can use hsl helper when defining custom properties for colors (opacity plugins disabled)', () => { - let config = { - content: [ - { - raw: html` -
-
-
-
-
-
-
-
-
-
-
-
- `, - }, - ], - theme: { - colors: { - primary: 'hsl(var(--color-primary) / )', - }, +it('can use hsl helper when defining custom properties for colors (opacity plugins disabled)', () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+
+
+
+
+
+
+
+
+ `, }, - corePlugins: { - backgroundOpacity: false, - borderOpacity: false, - divideOpacity: false, - placeholderOpacity: false, - textOpacity: false, - ringOpacity: false, + ], + theme: { + colors: { + primary: 'hsl(var(--color-primary) / )', }, - } + }, + corePlugins: { + backgroundOpacity: false, + borderOpacity: false, + divideOpacity: false, + placeholderOpacity: false, + textOpacity: false, + ringOpacity: false, + }, + } - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .divide-primary > :not([hidden]) ~ :not([hidden]) { - border-color: hsl(var(--color-primary) / 1); - } - .divide-primary\/50 > :not([hidden]) ~ :not([hidden]) { - border-color: hsl(var(--color-primary) / 0.5); - } - .border-primary { - border-color: hsl(var(--color-primary) / 1); - } - .border-primary\/50 { - border-color: hsl(var(--color-primary) / 0.5); - } - .bg-primary { - background-color: hsl(var(--color-primary) / 1); - } - .bg-primary\/50 { - background-color: hsl(var(--color-primary) / 0.5); - } - .text-primary { - color: hsl(var(--color-primary) / 1); - } - .text-primary\/50 { - color: hsl(var(--color-primary) / 0.5); - } - .placeholder-primary::placeholder { - color: hsl(var(--color-primary) / 1); - } - .placeholder-primary\/50::placeholder { - color: hsl(var(--color-primary) / 0.5); - } - .ring-primary { - --tw-ring-color: hsl(var(--color-primary) / 1); - } - .ring-primary\/50 { - --tw-ring-color: hsl(var(--color-primary) / 0.5); - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .divide-primary > :not([hidden]) ~ :not([hidden]) { + border-color: hsl(var(--color-primary) / 1); + } + .divide-primary\/50 > :not([hidden]) ~ :not([hidden]) { + border-color: hsl(var(--color-primary) / 0.5); + } + .border-primary { + border-color: hsl(var(--color-primary) / 1); + } + .border-primary\/50 { + border-color: hsl(var(--color-primary) / 0.5); + } + .bg-primary { + background-color: hsl(var(--color-primary) / 1); + } + .bg-primary\/50 { + background-color: hsl(var(--color-primary) / 0.5); + } + .text-primary { + color: hsl(var(--color-primary) / 1); + } + .text-primary\/50 { + color: hsl(var(--color-primary) / 0.5); + } + .placeholder-primary::placeholder { + color: hsl(var(--color-primary) / 1); + } + .placeholder-primary\/50::placeholder { + color: hsl(var(--color-primary) / 0.5); + } + .ring-primary { + --tw-ring-color: hsl(var(--color-primary) / 1); + } + .ring-primary\/50 { + --tw-ring-color: hsl(var(--color-primary) / 0.5); + } + `) }) +}) - test('Theme function in JS can apply alpha values to colors (1)', () => { - let input = css` - @tailwind utilities; - ` +test('Theme function in JS can apply alpha values to colors (1)', () => { + let input = css` + @tailwind utilities; + ` - let output = css` - .text-foo { - color: #3b82f680; - } - ` + let output = css` + .text-foo { + color: #3b82f680; + } + ` - return run(input, { - content: [{ raw: html`text-foo` }], - corePlugins: { textOpacity: false }, - theme: { - colors: { blue: { 500: '#3b82f6' } }, - extend: { - textColor: ({ theme }) => ({ - foo: theme('colors.blue.500 / 50%'), - }), - }, + return run(input, { + content: [{ raw: html`text-foo` }], + corePlugins: { textOpacity: false }, + theme: { + colors: { blue: { 500: '#3b82f6' } }, + extend: { + textColor: ({ theme }) => ({ + foo: theme('colors.blue.500 / 50%'), + }), }, - }).then((result) => { - expect(result.css).toMatchFormattedCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchFormattedCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('Theme function in JS can apply alpha values to colors (2)', () => { - let input = css` - @tailwind utilities; - ` +test('Theme function in JS can apply alpha values to colors (2)', () => { + let input = css` + @tailwind utilities; + ` - let output = css` - .text-foo { - color: #3b82f680; - } - ` + let output = css` + .text-foo { + color: #3b82f680; + } + ` - return run(input, { - content: [{ raw: html`text-foo` }], - corePlugins: { textOpacity: false }, - theme: { - colors: { blue: { 500: '#3b82f6' } }, - extend: { - textColor: ({ theme }) => ({ - foo: theme('colors.blue.500 / 0.5'), - }), - }, + return run(input, { + content: [{ raw: html`text-foo` }], + corePlugins: { textOpacity: false }, + theme: { + colors: { blue: { 500: '#3b82f6' } }, + extend: { + textColor: ({ theme }) => ({ + foo: theme('colors.blue.500 / 0.5'), + }), }, - }).then((result) => { - expect(result.css).toMatchFormattedCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchFormattedCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('Theme function in JS can apply alpha values to colors (3)', () => { - let input = css` - @tailwind utilities; - ` +test('Theme function in JS can apply alpha values to colors (3)', () => { + let input = css` + @tailwind utilities; + ` - let output = css` - .text-foo { - color: rgb(59 130 246 / var(--my-alpha)); - } - ` + let output = css` + .text-foo { + color: rgb(59 130 246 / var(--my-alpha)); + } + ` - return run(input, { - content: [{ raw: html`text-foo` }], - corePlugins: { textOpacity: false }, - theme: { - colors: { blue: { 500: '#3b82f6' } }, - extend: { - textColor: ({ theme }) => ({ - foo: theme('colors.blue.500 / var(--my-alpha)'), - }), - }, + return run(input, { + content: [{ raw: html`text-foo` }], + corePlugins: { textOpacity: false }, + theme: { + colors: { blue: { 500: '#3b82f6' } }, + extend: { + textColor: ({ theme }) => ({ + foo: theme('colors.blue.500 / var(--my-alpha)'), + }), }, - }).then((result) => { - expect(result.css).toMatchFormattedCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchFormattedCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('Theme function in JS can apply alpha values to colors (4)', () => { - let input = css` - @tailwind utilities; - ` +test('Theme function in JS can apply alpha values to colors (4)', () => { + let input = css` + @tailwind utilities; + ` - let output = css` - .text-foo { - color: #3c83f680; - } - ` + let output = css` + .text-foo { + color: #3c83f680; + } + ` - return run(input, { - content: [{ raw: html`text-foo` }], - corePlugins: { textOpacity: false }, - theme: { - colors: { blue: { 500: 'hsl(217, 91%, 60%)' } }, - extend: { - textColor: ({ theme }) => ({ - foo: theme('colors.blue.500 / 50%'), - }), - }, + return run(input, { + content: [{ raw: html`text-foo` }], + corePlugins: { textOpacity: false }, + theme: { + colors: { blue: { 500: 'hsl(217, 91%, 60%)' } }, + extend: { + textColor: ({ theme }) => ({ + foo: theme('colors.blue.500 / 50%'), + }), }, - }).then((result) => { - expect(result.css).toMatchFormattedCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchFormattedCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('Theme function in JS can apply alpha values to colors (5)', () => { - let input = css` - @tailwind utilities; - ` +test('Theme function in JS can apply alpha values to colors (5)', () => { + let input = css` + @tailwind utilities; + ` - let output = css` - .text-foo { - color: #3c83f680; - } - ` + let output = css` + .text-foo { + color: #3c83f680; + } + ` - return run(input, { - content: [{ raw: html`text-foo` }], - corePlugins: { textOpacity: false }, - theme: { - colors: { blue: { 500: 'hsl(217, 91%, 60%)' } }, - extend: { - textColor: ({ theme }) => ({ - foo: theme('colors.blue.500 / 0.5'), - }), - }, + return run(input, { + content: [{ raw: html`text-foo` }], + corePlugins: { textOpacity: false }, + theme: { + colors: { blue: { 500: 'hsl(217, 91%, 60%)' } }, + extend: { + textColor: ({ theme }) => ({ + foo: theme('colors.blue.500 / 0.5'), + }), }, - }).then((result) => { - expect(result.css).toMatchFormattedCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchFormattedCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('Theme function in JS can apply alpha values to colors (6)', () => { - let input = css` - @tailwind utilities; - ` +test('Theme function in JS can apply alpha values to colors (6)', () => { + let input = css` + @tailwind utilities; + ` - let output = css` - .text-foo { - color: hsl(217 91% 60% / var(--my-alpha)); - } - ` + let output = css` + .text-foo { + color: hsl(217 91% 60% / var(--my-alpha)); + } + ` - return run(input, { - content: [{ raw: html`text-foo` }], - corePlugins: { textOpacity: false }, - theme: { - colors: { blue: { 500: 'hsl(217, 91%, 60%)' } }, - extend: { - textColor: ({ theme }) => ({ - foo: theme('colors.blue.500 / var(--my-alpha)'), - }), - }, + return run(input, { + content: [{ raw: html`text-foo` }], + corePlugins: { textOpacity: false }, + theme: { + colors: { blue: { 500: 'hsl(217, 91%, 60%)' } }, + extend: { + textColor: ({ theme }) => ({ + foo: theme('colors.blue.500 / var(--my-alpha)'), + }), }, - }).then((result) => { - expect(result.css).toMatchFormattedCss(output) - expect(result.warnings().length).toBe(0) - }) + }, + }).then((result) => { + expect(result.css).toMatchFormattedCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('Theme function in JS can apply alpha values to colors (7)', () => { - let input = css` - @tailwind utilities; - ` +test('Theme function in JS can apply alpha values to colors (7)', () => { + let input = css` + @tailwind utilities; + ` - let output = css` - .text-foo { - color: rgb(var(--foo) / var(--my-alpha)); - } - ` + let output = css` + .text-foo { + color: rgb(var(--foo) / var(--my-alpha)); + } + ` - return run(input, { - content: [{ raw: html`text-foo` }], - corePlugins: { textOpacity: false }, - theme: { - colors: { - blue: { - 500: 'rgb(var(--foo) / )', - }, - }, - extend: { - textColor: ({ theme }) => ({ - foo: theme('colors.blue.500 / var(--my-alpha)'), - }), + return run(input, { + content: [{ raw: html`text-foo` }], + corePlugins: { textOpacity: false }, + theme: { + colors: { + blue: { + 500: 'rgb(var(--foo) / )', }, }, - }).then((result) => { - expect(result.css).toMatchFormattedCss(output) - expect(result.warnings().length).toBe(0) - }) + extend: { + textColor: ({ theme }) => ({ + foo: theme('colors.blue.500 / var(--my-alpha)'), + }), + }, + }, + }).then((result) => { + expect(result.css).toMatchFormattedCss(output) + expect(result.warnings().length).toBe(0) }) +}) - test('Theme function prefers existing values in config', () => { - let input = css` - @tailwind utilities; - ` +test('Theme function prefers existing values in config', () => { + let input = css` + @tailwind utilities; + ` - let output = css` - .text-foo { - color: purple; - } - ` + let output = css` + .text-foo { + color: purple; + } + ` - return run(input, { - content: [{ raw: html`text-foo` }], - corePlugins: { textOpacity: false }, - theme: { - colors: { - blue: { - '500 / 50%': 'purple', - }, - }, - extend: { - textColor: ({ theme }) => ({ - foo: theme('colors.blue.500 / 50%'), - }), + return run(input, { + content: [{ raw: html`text-foo` }], + corePlugins: { textOpacity: false }, + theme: { + colors: { + blue: { + '500 / 50%': 'purple', }, }, - }).then((result) => { - expect(result.css).toMatchFormattedCss(output) - expect(result.warnings().length).toBe(0) - }) - }) - - it('should be possible to use an as part of the color definition', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: ['backgroundColor', 'backgroundOpacity'], - theme: { - colors: { - primary: 'rgb(var(--color-primary) / )', - }, + extend: { + textColor: ({ theme }) => ({ + foo: theme('colors.blue.500 / 50%'), + }), }, - } - - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .bg-primary { - --tw-bg-opacity: 1; - background-color: rgb(var(--color-primary) / var(--tw-bg-opacity)); - } - `) - }) + }, + }).then((result) => { + expect(result.css).toMatchFormattedCss(output) + expect(result.warnings().length).toBe(0) }) +}) - it('should be possible to use an as part of the color definition with an opacity modifiers', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: ['backgroundColor', 'backgroundOpacity'], - theme: { - colors: { - primary: 'rgb(var(--color-primary) / )', - }, +it('should be possible to use an as part of the color definition', () => { + let config = { + content: [ + { + raw: html`
`, }, - } + ], + corePlugins: ['backgroundColor', 'backgroundOpacity'], + theme: { + colors: { + primary: 'rgb(var(--color-primary) / )', + }, + }, + } - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .bg-primary\/50 { - background-color: rgb(var(--color-primary) / 0.5); - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .bg-primary { + --tw-bg-opacity: 1; + background-color: rgb(var(--color-primary) / var(--tw-bg-opacity)); + } + `) }) +}) - it('should be possible to use an as part of the color definition with an opacity modifiers', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: ['backgroundColor'], - theme: { - colors: { - primary: 'rgb(var(--color-primary) / )', - }, +it('should be possible to use an as part of the color definition with an opacity modifiers', () => { + let config = { + content: [ + { + raw: html`
`, }, - } + ], + corePlugins: ['backgroundColor', 'backgroundOpacity'], + theme: { + colors: { + primary: 'rgb(var(--color-primary) / )', + }, + }, + } - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .bg-primary { - background-color: rgb(var(--color-primary) / 1); - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .bg-primary\/50 { + background-color: rgb(var(--color-primary) / 0.5); + } + `) }) +}) - it('should be possible to use inside arbitrary values', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: ['backgroundColor', 'backgroundOpacity'], - theme: { - colors: { - primary: 'rgb(var(--color-primary) / )', - }, +it('should be possible to use an as part of the color definition with an opacity modifiers', () => { + let config = { + content: [ + { + raw: html`
`, }, - } + ], + corePlugins: ['backgroundColor'], + theme: { + colors: { + primary: 'rgb(var(--color-primary) / )', + }, + }, + } - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .bg-\[rgb\(var\(--color-primary\)\/\\)\]\/50 { - background-color: rgb(var(--color-primary) / 0.5); - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .bg-primary { + background-color: rgb(var(--color-primary) / 1); + } + `) }) +}) - it('Theme functions can reference values with slashes in brackets', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - theme: { - colors: { - 'a/b': '#000000', - }, - extend: { - backgroundColor: ({ theme }) => ({ - foo1: theme('colors[a/b]'), - foo2: theme('colors[a/b]/50%'), - }), - }, +it('should be possible to use inside arbitrary values', () => { + let config = { + content: [ + { + raw: html`
`, }, - } + ], + corePlugins: ['backgroundColor', 'backgroundOpacity'], + theme: { + colors: { + primary: 'rgb(var(--color-primary) / )', + }, + }, + } - return run('@tailwind utilities', config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .bg-foo1 { - --tw-bg-opacity: 1; - background-color: rgb(0 0 0 / var(--tw-bg-opacity)); - } - .bg-foo2 { - background-color: #00000080; - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .bg-foo1 { - background-color: #000; - } - .bg-foo2 { - background-color: #00000080; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .bg-\[rgb\(var\(--color-primary\)\/\\)\]\/50 { + background-color: rgb(var(--color-primary) / 0.5); + } + `) }) +}) - it('works with opacity values defined as a placeholder or a function in when colors is a function', () => { - let config = { - content: [ - { - raw: html` -
- `, - }, - ], - theme: { - colors: () => ({ - foobar1: ({ opacityValue }) => `rgb(255 100 0 / ${opacityValue ?? '100%'})`, - foobar2: `rgb(255 100 0 / )`, - foobar3: { - 100: ({ opacityValue }) => `rgb(255 100 0 / ${opacityValue ?? '100%'})`, - 200: `rgb(255 100 0 / )`, - }, +it('Theme functions can reference values with slashes in brackets', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + theme: { + colors: { + 'a/b': '#000000', + }, + extend: { + backgroundColor: ({ theme }) => ({ + foo1: theme('colors[a/b]'), + foo2: theme('colors[a/b]/50%'), }), - extend: { - backgroundColor: ({ theme }) => ({ - foo10: theme('colors.foobar1'), - foo20: theme('colors.foobar2'), - foo30: theme('colors.foobar3.100'), - foo40: theme('colors.foobar3.200'), - foo11: theme('colors.foobar1 / 50%'), - foo21: theme('colors.foobar2 / 50%'), - foo31: theme('colors.foobar3.100 / 50%'), - foo41: theme('colors.foobar3.200 / 50%'), - }), - }, }, - } + }, + } - return run('@tailwind utilities', config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .bg-foo10 { - background-color: #ff6400; - } - .bg-foo11 { - background-color: #ff640080; - } - .bg-foo20 { - --tw-bg-opacity: 1; - background-color: rgb(255 100 0 / var(--tw-bg-opacity)); - } - .bg-foo21 { - background-color: #ff640080; - } - .bg-foo30 { - background-color: #ff6400; - } - .bg-foo31 { - background-color: #ff640080; - } - .bg-foo40 { - --tw-bg-opacity: 1; - background-color: rgb(255 100 0 / var(--tw-bg-opacity)); - } - .bg-foo41 { - background-color: #ff640080; - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .bg-foo10 { - background-color: #ff6400; - } - .bg-foo11 { - background-color: #ff640080; - } - .bg-foo20 { - background-color: #ff6400; - } - .bg-foo21 { - background-color: #ff640080; - } - .bg-foo30 { - background-color: #ff6400; - } - .bg-foo31 { - background-color: #ff640080; - } - .bg-foo40 { - background-color: #ff6400; - } - .bg-foo41 { - background-color: #ff640080; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .bg-foo1 { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); + } + .bg-foo2 { + background-color: #00000080; + } + `) }) +}) - it('The disableColorOpacityUtilitiesByDefault flag disables the color opacity plugins and removes their variables', () => { - let config = { - future: { - disableColorOpacityUtilitiesByDefault: true, +it('works with opacity values defined as a placeholder or a function in when colors is a function', () => { + let config = { + content: [ + { + raw: html` +
+ `, }, - content: [ - { - raw: html` -
-
-
-
- `, + ], + theme: { + colors: () => ({ + foobar1: ({ opacityValue }) => `rgb(255 100 0 / ${opacityValue ?? '100%'})`, + foobar2: `rgb(255 100 0 / )`, + foobar3: { + 100: ({ opacityValue }) => `rgb(255 100 0 / ${opacityValue ?? '100%'})`, + 200: `rgb(255 100 0 / )`, }, - ], - } + }), + extend: { + backgroundColor: ({ theme }) => ({ + foo10: theme('colors.foobar1'), + foo20: theme('colors.foobar2'), + foo30: theme('colors.foobar3.100'), + foo40: theme('colors.foobar3.200'), + foo11: theme('colors.foobar1 / 50%'), + foo21: theme('colors.foobar2 / 50%'), + foo31: theme('colors.foobar3.100 / 50%'), + foo41: theme('colors.foobar3.200 / 50%'), + }), + }, + }, + } - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .divide-blue-300 > :not([hidden]) ~ :not([hidden]) { - border-color: #93c5fd; - } - .divide-blue-300\/50 > :not([hidden]) ~ :not([hidden]) { - border-color: #93c5fd80; - } - .divide-blue-300\/\[var\(--my-opacity\)\] > :not([hidden]) ~ :not([hidden]) { - border-color: rgb(147 197 253 / var(--my-opacity)); - } - .border-blue-300 { - border-color: #93c5fd; - } - .border-blue-300\/50 { - border-color: #93c5fd80; - } - .border-blue-300\/\[var\(--my-opacity\)\] { - border-color: rgb(147 197 253 / var(--my-opacity)); - } - .bg-blue-300 { - background-color: #93c5fd; - } - .bg-blue-300\/50 { - background-color: #93c5fd80; - } - .bg-blue-300\/\[var\(--my-opacity\)\] { - background-color: rgb(147 197 253 / var(--my-opacity)); - } - .text-blue-300 { - color: #93c5fd; - } - .text-blue-300\/50 { - color: #93c5fd80; - } - .text-blue-300\/\[var\(--my-opacity\)\] { - color: rgb(147 197 253 / var(--my-opacity)); - } - .placeholder-blue-300::placeholder { - color: #93c5fd; - } - .placeholder-blue-300\/50::placeholder { - color: #93c5fd80; - } - .placeholder-blue-300\/\[var\(--my-opacity\)\]::placeholder { - color: rgb(147 197 253 / var(--my-opacity)); - } - .ring-blue-300 { - --tw-ring-color: #93c5fd; - } - .ring-blue-300\/50 { - --tw-ring-color: #93c5fd80; - } - .ring-blue-300\/\[var\(--my-opacity\)\] { - --tw-ring-color: rgb(147 197 253 / var(--my-opacity)); - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .bg-foo10 { + background-color: #ff6400; + } + .bg-foo11 { + background-color: #ff640080; + } + .bg-foo20 { + --tw-bg-opacity: 1; + background-color: rgb(255 100 0 / var(--tw-bg-opacity)); + } + .bg-foo21 { + background-color: #ff640080; + } + .bg-foo30 { + background-color: #ff6400; + } + .bg-foo31 { + background-color: #ff640080; + } + .bg-foo40 { + --tw-bg-opacity: 1; + background-color: rgb(255 100 0 / var(--tw-bg-opacity)); + } + .bg-foo41 { + background-color: #ff640080; + } + `) }) +}) - it('You can re-enable any opacity plugin even when disableColorOpacityUtilitiesByDefault is enabled', () => { - let config = { - future: { - disableColorOpacityUtilitiesByDefault: true, - }, - corePlugins: { - backgroundOpacity: true, - borderOpacity: true, - divideOpacity: true, - placeholderOpacity: true, - ringOpacity: true, - textOpacity: true, +it('The disableColorOpacityUtilitiesByDefault flag disables the color opacity plugins and removes their variables', () => { + let config = { + future: { + disableColorOpacityUtilitiesByDefault: true, + }, + content: [ + { + raw: html` +
+
+
+
+ `, }, - content: [ - { - raw: html` -
-
-
-
- `, - }, - ], - } + ], + } - return run('@tailwind utilities', config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .divide-blue-300 > :not([hidden]) ~ :not([hidden]) { - --tw-divide-opacity: 1; - border-color: rgb(147 197 253 / var(--tw-divide-opacity)); - } - .divide-blue-300\/50 > :not([hidden]) ~ :not([hidden]) { - border-color: #93c5fd80; - } - .divide-blue-300\/\[var\(--my-opacity\)\] > :not([hidden]) ~ :not([hidden]) { - border-color: rgb(147 197 253 / var(--my-opacity)); - } - .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) { - --tw-divide-opacity: 0.5; - } - .border-blue-300 { - --tw-border-opacity: 1; - border-color: rgb(147 197 253 / var(--tw-border-opacity)); - } - .border-blue-300\/50 { - border-color: #93c5fd80; - } - .border-blue-300\/\[var\(--my-opacity\)\] { - border-color: rgb(147 197 253 / var(--my-opacity)); - } - .border-opacity-50 { - --tw-border-opacity: 0.5; - } - .bg-blue-300 { - --tw-bg-opacity: 1; - background-color: rgb(147 197 253 / var(--tw-bg-opacity)); - } - .bg-blue-300\/50 { - background-color: #93c5fd80; - } - .bg-blue-300\/\[var\(--my-opacity\)\] { - background-color: rgb(147 197 253 / var(--my-opacity)); - } - .bg-opacity-50 { - --tw-bg-opacity: 0.5; - } - .text-blue-300 { - --tw-text-opacity: 1; - color: rgb(147 197 253 / var(--tw-text-opacity)); - } - .text-blue-300\/50 { - color: #93c5fd80; - } - .text-blue-300\/\[var\(--my-opacity\)\] { - color: rgb(147 197 253 / var(--my-opacity)); - } - .text-opacity-50 { - --tw-text-opacity: 0.5; - } - .placeholder-blue-300::placeholder { - --tw-placeholder-opacity: 1; - color: rgb(147 197 253 / var(--tw-placeholder-opacity)); - } - .placeholder-blue-300\/50::placeholder { - color: #93c5fd80; - } - .placeholder-blue-300\/\[var\(--my-opacity\)\]::placeholder { - color: rgb(147 197 253 / var(--my-opacity)); - } - .placeholder-opacity-50::placeholder { - --tw-placeholder-opacity: 0.5; - } - .ring-blue-300 { - --tw-ring-opacity: 1; - --tw-ring-color: rgb(147 197 253 / var(--tw-ring-opacity)); - } - .ring-blue-300\/50 { - --tw-ring-color: #93c5fd80; - } - .ring-blue-300\/\[var\(--my-opacity\)\] { - --tw-ring-color: rgb(147 197 253 / var(--my-opacity)); - } - .ring-opacity-50 { - --tw-ring-opacity: 0.5; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .divide-blue-300 > :not([hidden]) ~ :not([hidden]) { + border-color: #93c5fd; + } + .divide-blue-300\/50 > :not([hidden]) ~ :not([hidden]) { + border-color: #93c5fd80; + } + .divide-blue-300\/\[var\(--my-opacity\)\] > :not([hidden]) ~ :not([hidden]) { + border-color: rgb(147 197 253 / var(--my-opacity)); + } + .border-blue-300 { + border-color: #93c5fd; + } + .border-blue-300\/50 { + border-color: #93c5fd80; + } + .border-blue-300\/\[var\(--my-opacity\)\] { + border-color: rgb(147 197 253 / var(--my-opacity)); + } + .bg-blue-300 { + background-color: #93c5fd; + } + .bg-blue-300\/50 { + background-color: #93c5fd80; + } + .bg-blue-300\/\[var\(--my-opacity\)\] { + background-color: rgb(147 197 253 / var(--my-opacity)); + } + .text-blue-300 { + color: #93c5fd; + } + .text-blue-300\/50 { + color: #93c5fd80; + } + .text-blue-300\/\[var\(--my-opacity\)\] { + color: rgb(147 197 253 / var(--my-opacity)); + } + .placeholder-blue-300::placeholder { + color: #93c5fd; + } + .placeholder-blue-300\/50::placeholder { + color: #93c5fd80; + } + .placeholder-blue-300\/\[var\(--my-opacity\)\]::placeholder { + color: rgb(147 197 253 / var(--my-opacity)); + } + .ring-blue-300 { + --tw-ring-color: #93c5fd; + } + .ring-blue-300\/50 { + --tw-ring-color: #93c5fd80; + } + .ring-blue-300\/\[var\(--my-opacity\)\] { + --tw-ring-color: rgb(147 197 253 / var(--my-opacity)); + } + `) }) +}) - it('can replace the potential alpha value in rgba/hsla syntax', async () => { - let config = { - content: [{ raw: html`
` }], - theme: { - colors: { - 'primary-rgba': 'rgba(var(--color), 0.1)', - 'primary-hsla': 'hsla(var(--color), 0.1)', - }, +it('You can re-enable any opacity plugin even when disableColorOpacityUtilitiesByDefault is enabled', () => { + let config = { + future: { + disableColorOpacityUtilitiesByDefault: true, + }, + corePlugins: { + backgroundOpacity: true, + borderOpacity: true, + divideOpacity: true, + placeholderOpacity: true, + ringOpacity: true, + textOpacity: true, + }, + content: [ + { + raw: html` +
+
+
+
+ `, }, - } - - let result = await run('@tailwind utilities', config) + ], + } + return run('@tailwind utilities', config).then((result) => { expect(result.css).toMatchFormattedCss(css` - .text-primary-hsla\/50 { - color: hsla(var(--color), 0.5); + .divide-blue-300 > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 1; + border-color: rgb(147 197 253 / var(--tw-divide-opacity)); + } + .divide-blue-300\/50 > :not([hidden]) ~ :not([hidden]) { + border-color: #93c5fd80; + } + .divide-blue-300\/\[var\(--my-opacity\)\] > :not([hidden]) ~ :not([hidden]) { + border-color: rgb(147 197 253 / var(--my-opacity)); + } + .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 0.5; + } + .border-blue-300 { + --tw-border-opacity: 1; + border-color: rgb(147 197 253 / var(--tw-border-opacity)); + } + .border-blue-300\/50 { + border-color: #93c5fd80; + } + .border-blue-300\/\[var\(--my-opacity\)\] { + border-color: rgb(147 197 253 / var(--my-opacity)); + } + .border-opacity-50 { + --tw-border-opacity: 0.5; + } + .bg-blue-300 { + --tw-bg-opacity: 1; + background-color: rgb(147 197 253 / var(--tw-bg-opacity)); + } + .bg-blue-300\/50 { + background-color: #93c5fd80; + } + .bg-blue-300\/\[var\(--my-opacity\)\] { + background-color: rgb(147 197 253 / var(--my-opacity)); + } + .bg-opacity-50 { + --tw-bg-opacity: 0.5; + } + .text-blue-300 { + --tw-text-opacity: 1; + color: rgb(147 197 253 / var(--tw-text-opacity)); + } + .text-blue-300\/50 { + color: #93c5fd80; } - .text-primary-rgba\/50 { - color: rgba(var(--color), 0.5); + .text-blue-300\/\[var\(--my-opacity\)\] { + color: rgb(147 197 253 / var(--my-opacity)); + } + .text-opacity-50 { + --tw-text-opacity: 0.5; + } + .placeholder-blue-300::placeholder { + --tw-placeholder-opacity: 1; + color: rgb(147 197 253 / var(--tw-placeholder-opacity)); + } + .placeholder-blue-300\/50::placeholder { + color: #93c5fd80; + } + .placeholder-blue-300\/\[var\(--my-opacity\)\]::placeholder { + color: rgb(147 197 253 / var(--my-opacity)); + } + .placeholder-opacity-50::placeholder { + --tw-placeholder-opacity: 0.5; + } + .ring-blue-300 { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(147 197 253 / var(--tw-ring-opacity)); + } + .ring-blue-300\/50 { + --tw-ring-color: #93c5fd80; + } + .ring-blue-300\/\[var\(--my-opacity\)\] { + --tw-ring-color: rgb(147 197 253 / var(--my-opacity)); + } + .ring-opacity-50 { + --tw-ring-opacity: 0.5; } `) }) }) +it('can replace the potential alpha value in rgba/hsla syntax', async () => { + let config = { + content: [{ raw: html`
` }], + theme: { + colors: { + 'primary-rgba': 'rgba(var(--color), 0.1)', + 'primary-hsla': 'hsla(var(--color), 0.1)', + }, + }, + } + + let result = await run('@tailwind utilities', config) + + expect(result.css).toMatchFormattedCss(css` + .text-primary-hsla\/50 { + color: hsla(var(--color), 0.5); + } + .text-primary-rgba\/50 { + color: rgba(var(--color), 0.5); + } + `) +}) + it('variables with variable fallback values can use opacity modifier', async () => { let config = { content: [ diff --git a/tests/parallel-variants.test.js b/tests/parallel-variants.test.js index a4d4dfc8f1b2..61fb2cb336af 100644 --- a/tests/parallel-variants.test.js +++ b/tests/parallel-variants.test.js @@ -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`
`, - }, - ], - plugins: [ - function test({ addVariant }) { - addVariant('test', ['& *::test', '&::test']) - }, - ], - } +test('basic parallel variants', async () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + 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`
`, - }, - ], - 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`
`, + }, + ], + 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`
`, - }, - ], - plugins: [ - function test({ addVariant }) { - addVariant('test', ({ container }) => { - container.walkDecls((decl) => { - decl.value = `calc(0 + ${decl.value})` - }) - - return ['& *::test', '&::test'] +test('a function that returns parallel variants can modify the container', async () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + plugins: [ + function test({ addVariant }) { + addVariant('test', ({ container }) => { + container.walkDecls((decl) => { + decl.value = `calc(0 + ${decl.value})` }) - }, - ], - } - 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 ['& *::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; + } + `) }) }) diff --git a/tests/parseAnimationValue.test.js b/tests/parseAnimationValue.test.js index 098178079e81..293d6fbec60a 100644 --- a/tests/parseAnimationValue.test.js +++ b/tests/parseAnimationValue.test.js @@ -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)'], - }) - }) + }, + ], + [ + '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('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('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('duration & delay', () => { - it.each([ - // Positive seconds (integer) - ['spin 2s 1s linear', { duration: '2s', delay: '1s' }], +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) + }) +}) - // Negative seconds (integer) - ['spin -2s -1s linear', { duration: '-2s', delay: '-1s' }], +describe('duration & delay', () => { + it.each([ + // Positive 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 (integer) + ['spin -2s -1s linear', { duration: '-2s', delay: '-1s' }], - // Negative seconds (float) - ['spin -2.321s -1.321s linear', { duration: '-2.321s', delay: '-1.321s' }], + // Positive 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 seconds (float) + ['spin -2.321s -1.321s linear', { duration: '-2.321s', delay: '-1.321s' }], - // Negative milliseconds (integer) - ['spin -200ms -100ms linear', { duration: '-200ms', delay: '-100ms' }], + // Positive 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 (integer) + ['spin -200ms -100ms linear', { duration: '-200ms', delay: '-100ms' }], - // 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) - }) - }) + // Positive milliseconds (float) + ['spin 200.321ms 100.321ms linear', { duration: '200.321ms', delay: '100.321ms' }], - 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) - } - ) + // 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('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(',') - +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(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', - }, - ]) - }) - }) + expect(parsed).toHaveLength(1) + expect(parsed[0].iterationCount).toEqual(iterationCount) + } + ) +}) - 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) +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) +}) diff --git a/tests/parseObjectStyles.test.js b/tests/parseObjectStyles.test.js index c6121977fa14..b823801eed5c 100644 --- a/tests/parseObjectStyles.test.js +++ b/tests/parseObjectStyles.test.js @@ -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', - }, - }) +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; - } - `) + expect(toCss(result)).toMatchFormattedCss(css` + .foobar { + color: #fff; + background-color: red; + padding: 1rem; + } + `) +}) + +test('it parses multiple class definitions', () => { + const result = parseObjectStyles({ + '.foo': { + backgroundColor: 'red', + color: 'white', + padding: '1rem', + }, + '.bar': { + width: '200px', + height: '100px', + }, }) - test('it parses multiple class definitions', () => { - const result = parseObjectStyles({ - '.foo': { - backgroundColor: 'red', - color: 'white', - padding: '1rem', + expect(toCss(result)).toMatchFormattedCss(css` + .foo { + color: #fff; + background-color: red; + 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', }, - '.bar': { - width: '200px', - height: '100px', + '&:focus': { + backgroundColor: 'blue', }, - }) - - expect(toCss(result)).toMatchFormattedCss(css` - .foo { - color: #fff; - background-color: red; - padding: 1rem; - } - .bar { - width: 200px; - height: 100px; - } - `) + }, }) - test('it parses nested pseudo-selectors', () => { - const result = parseObjectStyles({ + 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: 'red', - color: 'white', - padding: '1rem', - '&:hover': { - backgroundColor: 'orange', - }, - '&:focus': { - backgroundColor: 'blue', - }, + backgroundColor: 'orange', }, - }) + }, + }) - expect(toCss(result)).toMatchFormattedCss(css` + 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; - } + expect(toCss(result)).toMatchFormattedCss(css` + .foo { + color: #fff; + background-color: red; + padding: 1rem; + } + @media (min-width: 200px) { + .foo { + background-color: orange; } - `) - }) + } + `) +}) - test('it parses nested media queries', () => { - const result = parseObjectStyles({ - '.foo': { - backgroundColor: 'red', - color: 'white', - padding: '1rem', - '@media (min-width: 200px)': { - backgroundColor: '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` + expect(toCss(result)).toMatchFormattedCss(css` + .foo { + color: #fff; + background-color: red; + padding: 1rem; + } + @screen sm { .foo { - color: #fff; - background-color: red; - padding: 1rem; - } - @media (min-width: 200px) { - .foo { - background-color: orange; - } + background-color: orange; } - `) - }) + } + `) +}) - test('it bubbles nested screen rules', () => { - const result = parseObjectStyles({ - '.foo': { - backgroundColor: 'red', - color: 'white', - padding: '1rem', - '@screen sm': { +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; - } - @screen sm { - .foo { - background-color: 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 pseudo-selectors in nested media queries', () => { - const result = parseObjectStyles({ - '.foo': { - backgroundColor: 'red', - color: 'white', - padding: '1rem', - '&:hover': { - '@media (min-width: 200px)': { - backgroundColor: '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; - } - @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 { + background-color: 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', () => { + 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 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 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', - }, - }, +test('it strips empty selectors when nesting', () => { + const result = parseObjectStyles({ + '.foo': { + '.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', - }, - }, - }) - - expect(toCss(result)).toMatchFormattedCss(css` - .foo .bar { - background-color: orange; - } - `) - }) + expect(toCss(result)).toMatchFormattedCss(css` + .foo .bar { + background-color: orange; + } + `) +}) - test('it can parse an array of styles', () => { - const result = parseObjectStyles([ - { - '.foo': { - backgroundColor: 'orange', - }, +test('it can parse an array of styles', () => { + const result = parseObjectStyles([ + { + '.foo': { + backgroundColor: 'orange', }, - { - '.bar': { - backgroundColor: 'red', - }, + }, + { + '.bar': { + backgroundColor: 'red', }, - { - '.foo': { - backgroundColor: 'blue', - }, + }, + { + '.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', - }, - }) + expect(toCss(result)).toMatchFormattedCss(css` + .foo { + background-color: orange; + } + .bar { + background-color: red; + } + .foo { + background-color: #00f; + } + `) +}) - expect(toCss(result)).toMatchFormattedCss(css` - :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; + } + `) }) diff --git a/tests/plugins/divide.test.js b/tests/plugins/divide.test.js index 314233bea074..37bd012ac65a 100644 --- a/tests/plugins/divide.test.js +++ b/tests/plugins/divide.test.js @@ -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`
` }], - corePlugins: { preflight: false }, - } +it('should add the divide styles for divide-y and a default border color', () => { + let config = { + content: [{ raw: html`
` }], + 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`
` }], - 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))); - } - `) +it('should add the divide styles for divide-x and a default border color', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } - 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`
` }], - corePlugins: { preflight: false }, - } +it('should add the divide styles for divide-y-reverse and a default border color', () => { + let config = { + content: [{ raw: html`
` }], + 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`
` }], - corePlugins: { preflight: false }, - } +it('should add the divide styles for divide-x-reverse and a default border color', () => { + let config = { + content: [{ raw: html`
` }], + 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`
` }], - 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`
` }], + 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; + } + `) }) }) diff --git a/tests/plugins/fontFamily.test.js b/tests/plugins/fontFamily.test.js index 1170dac8e9e6..f5718909d213 100644 --- a/tests/plugins/fontFamily.test.js +++ b/tests/plugins/fontFamily.test.js @@ -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`
` }], - theme: { - fontFamily: { - sans: 'Helvetica, Arial, sans-serif', - }, +test('font-family utilities can be defined as a string', () => { + let config = { + content: [{ raw: html`
` }], + 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`
` }], - theme: { - fontFamily: { - sans: ['Helvetica', 'Arial', 'sans-serif'], - }, +test('font-family utilities can be defined as an array', () => { + let config = { + content: [{ raw: html`
` }], + 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`
` }], - theme: { - fontFamily: { - sans: ["'Exo 2'", 'sans-serif'], - }, +test('font-family values are not automatically escaped', () => { + let config = { + content: [{ raw: html`
` }], + 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`
` }], - 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`
` }], + 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`
` }], - 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`
` }], + 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; + } + `) }) }) diff --git a/tests/plugins/fontSize.test.js b/tests/plugins/fontSize.test.js index 08e88bb76846..32214650f83b 100644 --- a/tests/plugins/fontSize.test.js +++ b/tests/plugins/fontSize.test.js @@ -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`
` }], - 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`
` }], + 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`
` }], - 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`
` }], + 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`
` }], - 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`
` }], + 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`
` }], - 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`
` }], + 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`
-
-
-
-
-
-
`, - }, - ], - 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`
+
+
+
+
+
+
`, + }, + ], + 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; - 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; + 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-sm\/\[21px\] { - font-size: 12px; - line-height: 21px; + .md\:text-base\/7 { + font-size: 16px; + line-height: 28px; } - @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; } - `) - }) + } + `) }) }) diff --git a/tests/plugins/gradientColorStops.test.js b/tests/plugins/gradientColorStops.test.js index de79cc8fc033..c9647d50fb96 100644 --- a/tests/plugins/gradientColorStops.test.js +++ b/tests/plugins/gradientColorStops.test.js @@ -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`
`, - }, - ], - theme: { - colors: { - primary: ({ opacityVariable, opacityValue }) => { - if (opacityValue !== undefined) { - return `rgba(31,31,31,${opacityValue})` - } +test('opacity variables are given to colors defined as closures', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + 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))` - } + 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', + return `rgb(31,31,31)` }, + secondary: 'hsl(10, 50%, 50%)', + }, + opacity: { + 50: '0.5', }, - } + }, + } - 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; - } - `) - }) + 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` -
-
-
-
-
-
- `, - }, - ], - theme: {}, - } +test('gradient color stop position', () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+
+
+ `, + }, + ], + 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; - } - `) - }) + 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; + } + `) }) }) diff --git a/tests/prefers-contrast.test.js b/tests/prefers-contrast.test.js index e3b940da858c..52420ee1e395 100644 --- a/tests/prefers-contrast.test.js +++ b/tests/prefers-contrast.test.js @@ -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`
`, - }, - ], - corePlugins: { preflight: false }, - } +it('should be possible to use contrast-more and contrast-less variants', () => { + let config = { + content: [ + { + raw: 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) => { - 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(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)); - } + background-color: rgb(236 72 153 / 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; - } + } + @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`
` }], - corePlugins: { preflight: false }, - } +it('dark mode should appear after the contrast variants', () => { + let config = { + content: [{ raw: 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) => { - 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; - } + 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 { - background-color: #fff; - } + } + @media (prefers-color-scheme: dark) { + .dark\:bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); } - `) - }) + } + `) }) }) diff --git a/tests/prefix.test.js b/tests/prefix.test.js index a93e570ebbf7..ab580d192145 100644 --- a/tests/prefix.test.js +++ b/tests/prefix.test.js @@ -1,627 +1,611 @@ -import { crosscheck, run, html, css, defaults } from './util/run' - -crosscheck(({ stable, oxide }) => { - oxide.test.todo('prefix') - stable.test('prefix', () => { - let config = { - prefix: 'tw-', - darkMode: 'selector', - content: [ - { - raw: html` -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - theme: { - animation: { - spin: 'spin 1s linear infinite', - ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite', - }, - keyframes: { - spin: { to: { transform: 'rotate(360deg)' } }, - }, +import { run, html, css, defaults } from './util/run' + +test('prefix', () => { + let config = { + prefix: 'tw-', + darkMode: 'selector', + content: [ + { + raw: html` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + theme: { + animation: { + spin: 'spin 1s linear infinite', + ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite', + }, + keyframes: { + spin: { to: { transform: 'rotate(360deg)' } }, }, - plugins: [ - function ({ addComponents, addUtilities }) { - addComponents({ - '.btn-prefix': { + }, + plugins: [ + function ({ addComponents, addUtilities }) { + addComponents({ + '.btn-prefix': { + button: 'yes', + }, + }) + addComponents( + { + '.btn-no-prefix': { button: 'yes', }, - }) - addComponents( - { - '.btn-no-prefix': { - button: 'yes', - }, - }, - { respectPrefix: false } - ) - addUtilities({ - '.custom-util-prefix': { + }, + { respectPrefix: false } + ) + addUtilities({ + '.custom-util-prefix': { + button: 'no', + }, + }) + addUtilities( + { + '.custom-util-no-prefix': { button: 'no', }, - }) - addUtilities( - { - '.custom-util-no-prefix': { - button: 'no', - }, - }, - { respectPrefix: false } - ) - }, - ], - } - - let input = css` - @tailwind base; - @tailwind components; - @layer components { - .custom-component { - @apply tw-font-bold dark:group-hover:tw-font-normal; - } + }, + { respectPrefix: false } + ) + }, + ], + } + + let input = css` + @tailwind base; + @tailwind components; + @layer components { + .custom-component { + @apply tw-font-bold dark:group-hover:tw-font-normal; } - @tailwind utilities; - @layer utilities { - .custom-utility { - foo: bar; - } + } + @tailwind utilities; + @layer utilities { + .custom-utility { + foo: bar; } - ` + } + ` - 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%; + } + @media (min-width: 640px) { .tw-container { - width: 100%; - } - @media (min-width: 640px) { - .tw-container { - max-width: 640px; - } - } - @media (min-width: 768px) { - .tw-container { - max-width: 768px; - } - } - @media (min-width: 1024px) { - .tw-container { - max-width: 1024px; - } - } - @media (min-width: 1280px) { - .tw-container { - max-width: 1280px; - } - } - @media (min-width: 1536px) { - .tw-container { - max-width: 1536px; - } - } - .tw-btn-prefix, - .btn-no-prefix { - button: yes; + max-width: 640px; } - .custom-component { - font-weight: 700; - } - .tw-group:hover .custom-component:where(.tw-dark, .tw-dark *) { - font-weight: 400; - } - .tw--ml-4 { - margin-left: -1rem; - } - .tw-animate-ping { - animation: 1s cubic-bezier(0, 0, 0.2, 1) infinite ping; - } - @keyframes tw-spin { - to { - transform: rotate(360deg); - } - } - .tw-animate-spin { - animation: 1s linear infinite tw-spin; - } - .tw-font-bold { - font-weight: 700; + } + @media (min-width: 768px) { + .tw-container { + max-width: 768px; } - .tw-custom-util-prefix, - .custom-util-no-prefix { - button: no; + } + @media (min-width: 1024px) { + .tw-container { + max-width: 1024px; } - .tw-group:hover .group-hover\:focus-within\:tw-text-left:focus-within { - text-align: left; + } + @media (min-width: 1280px) { + .tw-container { + max-width: 1280px; } - @media (prefers-reduced-motion: no-preference) { - .motion-safe\:hover\:tw-text-center:hover { - text-align: center; - } + } + @media (min-width: 1536px) { + .tw-container { + max-width: 1536px; } - @media (min-width: 768px) { - .md\:tw--ml-5 { - margin-left: -1.25rem; - } - .md\:hover\:tw--ml-6:hover { - margin-left: -1.5rem; - } - .md\:hover\:tw-text-right:hover { - text-align: right; - } + } + .tw-btn-prefix, + .btn-no-prefix { + button: yes; + } + .custom-component { + font-weight: 700; + } + .tw-group:hover .custom-component:where(.tw-dark, .tw-dark *) { + font-weight: 400; + } + .tw--ml-4 { + margin-left: -1rem; + } + .tw-animate-ping { + animation: 1s cubic-bezier(0, 0, 0.2, 1) infinite ping; + } + @keyframes tw-spin { + to { + transform: rotate(360deg); } - .rtl\:active\:tw-text-center:active:where([dir='rtl'], [dir='rtl'] *) { + } + .tw-animate-spin { + animation: 1s linear infinite tw-spin; + } + .tw-font-bold { + font-weight: 700; + } + .tw-custom-util-prefix, + .custom-util-no-prefix { + button: no; + } + .tw-group:hover .group-hover\:focus-within\:tw-text-left:focus-within { + text-align: left; + } + @media (prefers-reduced-motion: no-preference) { + .motion-safe\:hover\:tw-text-center:hover { text-align: center; } - .dark\:tw-bg-\[rgb\(255\,0\,0\)\]:where(.tw-dark, .tw-dark *) { - --tw-bg-opacity: 1; - background-color: rgb(255 0 0 / var(--tw-bg-opacity)); + } + @media (min-width: 768px) { + .md\:tw--ml-5 { + margin-left: -1.25rem; + } + .md\:hover\:tw--ml-6:hover { + margin-left: -1.5rem; } - .dark\:focus\:tw-text-left:focus:where(.tw-dark, .tw-dark *) { - text-align: left; + .md\:hover\:tw-text-right:hover { + text-align: right; } - `) - }) + } + .rtl\:active\:tw-text-center:active:where([dir='rtl'], [dir='rtl'] *) { + text-align: center; + } + .dark\:tw-bg-\[rgb\(255\,0\,0\)\]:where(.tw-dark, .tw-dark *) { + --tw-bg-opacity: 1; + background-color: rgb(255 0 0 / var(--tw-bg-opacity)); + } + .dark\:focus\:tw-text-left:focus:where(.tw-dark, .tw-dark *) { + text-align: left; + } + `) }) +}) - oxide.test.todo('negative values: marker before prefix') - stable.test('negative values: marker before prefix', async () => { - let config = { - prefix: 'tw-', - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind utilities; - ` +test('negative values: marker before prefix', async () => { + let config = { + prefix: 'tw-', + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } - await run(input, config) + let input = css` + @tailwind utilities; + ` - const result = await run(input, config) + await run(input, config) - expect(result.css).toMatchFormattedCss(css` - .-tw-top-1 { - top: -0.25rem; - } - `) - }) + const result = await run(input, config) - oxide.test.todo('negative values: marker after prefix') - stable.test('negative values: marker after prefix', async () => { - let config = { - prefix: 'tw-', - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, + expect(result.css).toMatchFormattedCss(css` + .-tw-top-1 { + top: -0.25rem; } + `) +}) - let input = css` - @tailwind utilities; - ` +test('negative values: marker after prefix', async () => { + let config = { + prefix: 'tw-', + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } - await run(input, config) + let input = css` + @tailwind utilities; + ` - const result = await run(input, config) + await run(input, config) - expect(result.css).toMatchFormattedCss(css` - .tw--top-1 { - top: -0.25rem; - } - `) - }) + const result = await run(input, config) - oxide.test.todo('negative values: marker before prefix and arbitrary value') - stable.test('negative values: marker before prefix and arbitrary value', async () => { - let config = { - prefix: 'tw-', - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, + expect(result.css).toMatchFormattedCss(css` + .tw--top-1 { + top: -0.25rem; } + `) +}) - let input = css` - @tailwind utilities; - ` +test('negative values: marker before prefix and arbitrary value', async () => { + let config = { + prefix: 'tw-', + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } - await run(input, config) + let input = css` + @tailwind utilities; + ` - const result = await run(input, config) + await run(input, config) - expect(result.css).toMatchFormattedCss(css` - .-tw-top-\[1px\] { - top: -1px; - } - `) - }) + const result = await run(input, config) - oxide.test.todo('negative values: marker after prefix and arbitrary value') - stable.test('negative values: marker after prefix and arbitrary value', async () => { - let config = { - prefix: 'tw-', - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, + expect(result.css).toMatchFormattedCss(css` + .-tw-top-\[1px\] { + top: -1px; } + `) +}) - let input = css` - @tailwind utilities; - ` +test('negative values: marker after prefix and arbitrary value', async () => { + let config = { + prefix: 'tw-', + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } - await run(input, config) + let input = css` + @tailwind utilities; + ` - const result = await run(input, config) + await run(input, config) - expect(result.css).toMatchFormattedCss(css` - .tw--top-\[1px\] { - top: -1px; - } - `) - }) + const result = await run(input, config) - oxide.test.todo('negative values: no marker and arbitrary value') - stable.test('negative values: no marker and arbitrary value', async () => { - let config = { - prefix: 'tw-', - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, + expect(result.css).toMatchFormattedCss(css` + .tw--top-\[1px\] { + top: -1px; } + `) +}) - let input = css` - @tailwind utilities; - ` +test('negative values: no marker and arbitrary value', async () => { + let config = { + prefix: 'tw-', + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } - await run(input, config) + let input = css` + @tailwind utilities; + ` - const result = await run(input, config) + await run(input, config) - expect(result.css).toMatchFormattedCss(css` - .tw-top-\[-1px\] { - top: -1px; - } - `) - }) - - oxide.test.todo('negative values: variant versions') - stable.test('negative values: variant versions', async () => { - let config = { - prefix: 'tw-', - content: [ - { - raw: html` -
-
-
+ const result = await run(input, config) - -
- `, - }, - ], - corePlugins: { preflight: false }, + expect(result.css).toMatchFormattedCss(css` + .tw-top-\[-1px\] { + top: -1px; } + `) +}) + +test('negative values: variant versions', async () => { + let config = { + prefix: 'tw-', + content: [ + { + raw: html` +
+
+
- let input = css` - @tailwind utilities; - ` + +
+ `, + }, + ], + corePlugins: { preflight: false }, + } - await run(input, config) + let input = css` + @tailwind utilities; + ` - const result = await run(input, config) + await run(input, config) - expect(result.css).toMatchFormattedCss(css` - .hover\:-tw-top-1:hover { - top: -0.25rem; - } - .hover\:-tw-top-\[1px\]:hover { - top: -1px; - } - .hover\:tw--top-1:hover { - top: -0.25rem; - } - .hover\:tw--top-\[1px\]:hover, - .hover\:tw-top-\[-1px\]:hover { - top: -1px; - } - `) - }) + const result = await run(input, config) - oxide.test.todo('negative values: prefix and apply') - stable.test('negative values: prefix and apply', async () => { - let config = { - prefix: 'tw-', - content: [{ raw: html`` }], - corePlugins: { preflight: false }, + expect(result.css).toMatchFormattedCss(css` + .hover\:-tw-top-1:hover { + top: -0.25rem; + } + .hover\:-tw-top-\[1px\]:hover { + top: -1px; + } + .hover\:tw--top-1:hover { + top: -0.25rem; } + .hover\:tw--top-\[1px\]:hover, + .hover\:tw-top-\[-1px\]:hover { + top: -1px; + } + `) +}) - let input = css` - @tailwind utilities; +test('negative values: prefix and apply', async () => { + let config = { + prefix: 'tw-', + content: [{ raw: html`` }], + corePlugins: { preflight: false }, + } - .a { - @apply hover:tw--top-1; - } - .b { - @apply hover:-tw-top-1; - } - .c { - @apply hover:-tw-top-[1px]; - } - .d { - @apply hover:tw--top-[1px]; - } - .e { - @apply hover:tw-top-[-1px]; - } - ` + let input = css` + @tailwind utilities; - await run(input, config) + .a { + @apply hover:tw--top-1; + } + .b { + @apply hover:-tw-top-1; + } + .c { + @apply hover:-tw-top-[1px]; + } + .d { + @apply hover:tw--top-[1px]; + } + .e { + @apply hover:tw-top-[-1px]; + } + ` - const result = await run(input, config) + await run(input, config) - expect(result.css).toMatchFormattedCss(css` - .a:hover, - .b:hover { - top: -0.25rem; - } - .c:hover, - .d:hover, - .e:hover { - top: -1px; - } - `) - }) + const result = await run(input, config) - oxide.test.todo('negative values: prefix in the safelist') - stable.test('negative values: prefix in the safelist', async () => { - let config = { - prefix: 'tw-', - safelist: [{ pattern: /-tw-top-1/g }, { pattern: /tw--top-1/g }], - theme: { - inset: { - 1: '0.25rem', - }, - }, - content: [{ raw: html`` }], - corePlugins: { preflight: false }, + expect(result.css).toMatchFormattedCss(css` + .a:hover, + .b:hover { + top: -0.25rem; + } + .c:hover, + .d:hover, + .e:hover { + top: -1px; } + `) +}) - let input = css` - @tailwind utilities; - ` +test('negative values: prefix in the safelist', async () => { + let config = { + prefix: 'tw-', + safelist: [{ pattern: /-tw-top-1/g }, { pattern: /tw--top-1/g }], + theme: { + inset: { + 1: '0.25rem', + }, + }, + content: [{ raw: html`` }], + corePlugins: { preflight: false }, + } - await run(input, config) + let input = css` + @tailwind utilities; + ` - const result = await run(input, config) + await run(input, config) - expect(result.css).toMatchFormattedCss(css` - .-tw-top-1, - .tw--top-1 { - top: -0.25rem; - } - `) - }) + const result = await run(input, config) - oxide.test.todo('prefix with negative values and variants in the safelist') - stable.test('prefix with negative values and variants in the safelist', async () => { - let config = { - prefix: 'tw-', - safelist: [ - { pattern: /-tw-top-1/, variants: ['hover', 'sm:hover'] }, - { pattern: /tw--top-1/, variants: ['hover', 'sm:hover'] }, - ], - theme: { - inset: { - 1: '0.25rem', - }, - }, - content: [{ raw: html`` }], - corePlugins: { preflight: false }, + expect(result.css).toMatchFormattedCss(css` + .-tw-top-1, + .tw--top-1 { + top: -0.25rem; } + `) +}) - let input = css` - @tailwind utilities; - ` +test('prefix with negative values and variants in the safelist', async () => { + let config = { + prefix: 'tw-', + safelist: [ + { pattern: /-tw-top-1/, variants: ['hover', 'sm:hover'] }, + { pattern: /tw--top-1/, variants: ['hover', 'sm:hover'] }, + ], + theme: { + inset: { + 1: '0.25rem', + }, + }, + content: [{ raw: html`` }], + corePlugins: { preflight: false }, + } - await run(input, config) + let input = css` + @tailwind utilities; + ` - const result = await run(input, config) + await run(input, config) - expect(result.css).toMatchFormattedCss(css` - .-tw-top-1, - .tw--top-1, - .hover\:-tw-top-1:hover, - .hover\:tw--top-1:hover { + const result = await run(input, config) + + expect(result.css).toMatchFormattedCss(css` + .-tw-top-1, + .tw--top-1, + .hover\:-tw-top-1:hover, + .hover\:tw--top-1:hover { + top: -0.25rem; + } + @media (min-width: 640px) { + .sm\:hover\:-tw-top-1:hover, + .sm\:hover\:tw--top-1:hover { top: -0.25rem; } - @media (min-width: 640px) { - .sm\:hover\:-tw-top-1:hover, - .sm\:hover\:tw--top-1:hover { - top: -0.25rem; - } - } - `) - }) - - oxide.test.todo('prefix does not detect and generate unnecessary classes') - stable.test('prefix does not detect and generate unnecessary classes', async () => { - let config = { - prefix: 'tw-_', - content: [{ raw: html`-aaa-filter aaaa-table aaaa-hidden` }], - corePlugins: { preflight: false }, } + `) +}) - let input = css` - @tailwind utilities; - ` +test('prefix does not detect and generate unnecessary classes', async () => { + let config = { + prefix: 'tw-_', + content: [{ raw: html`-aaa-filter aaaa-table aaaa-hidden` }], + corePlugins: { preflight: false }, + } - const result = await run(input, config) + let input = css` + @tailwind utilities; + ` - expect(result.css).toMatchFormattedCss(css``) - }) + const result = await run(input, config) - oxide.test.todo('supports prefixed utilities using arbitrary values') - stable.test('supports prefixed utilities using arbitrary values', async () => { - let config = { - prefix: 'tw-', - content: [{ raw: html`foo` }], - corePlugins: { preflight: false }, - } + expect(result.css).toMatchFormattedCss(css``) +}) - let input = css` - .foo { - @apply tw-text-[color:rgb(var(--button-background,var(--primary-button-background)))]; - @apply tw-ease-[cubic-bezier(0.77,0,0.175,1)]; - @apply tw-rounded-[min(4px,var(--input-border-radius))]; - } - ` +test('supports prefixed utilities using arbitrary values', async () => { + let config = { + prefix: 'tw-', + content: [{ raw: html`foo` }], + corePlugins: { preflight: false }, + } - const result = await run(input, config) + let input = css` + .foo { + @apply tw-text-[color:rgb(var(--button-background,var(--primary-button-background)))]; + @apply tw-ease-[cubic-bezier(0.77,0,0.175,1)]; + @apply tw-rounded-[min(4px,var(--input-border-radius))]; + } + ` - expect(result.css).toMatchFormattedCss(css` - .foo { - color: rgb(var(--button-background, var(--primary-button-background))); - border-radius: min(4px, var(--input-border-radius)); - transition-timing-function: cubic-bezier(0.77, 0, 0.175, 1); - } - `) - }) + const result = await run(input, config) - oxide.test.todo('supports non-word prefixes (1)') - stable.test('supports non-word prefixes (1)', async () => { - let config = { - prefix: '@', - content: [ - { - raw: html` -
-
-
-
-
-
- - -
- `, - }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind utilities; - @layer utilities { - .my-utility { - color: orange; - } - } - .foo { - @apply @text-white; - @apply [background-color:red]; - } - ` + expect(result.css).toMatchFormattedCss(css` + .foo { + color: rgb(var(--button-background, var(--primary-button-background))); + border-radius: min(4px, var(--input-border-radius)); + transition-timing-function: cubic-bezier(0.77, 0, 0.175, 1); + } + `) +}) - const result = await run(input, config) +test('supports non-word prefixes (1)', async () => { + let config = { + prefix: '@', + content: [ + { + raw: html` +
+
+
+
+
+
+ + +
+ `, + }, + ], + corePlugins: { preflight: false }, + } - expect(result.css).toMatchFormattedCss(css` - .\@bg-black { - --tw-bg-opacity: 1; - background-color: rgb(0 0 0 / var(--tw-bg-opacity)); - } - .\@underline { - text-decoration-line: underline; - } + let input = css` + @tailwind utilities; + @layer utilities { .my-utility { color: orange; } - .foo { - --tw-text-opacity: 1; - color: rgb(255 255 255 / var(--tw-text-opacity)); - background-color: red; - } - .hover\:before\:\@content-\[\'Hovering\'\]:hover:before { - --tw-content: 'Hovering'; - content: var(--tw-content); - } - `) - }) + } + .foo { + @apply @text-white; + @apply [background-color:red]; + } + ` - oxide.test.todo('supports non-word prefixes (2)') - stable.test('supports non-word prefixes (2)', async () => { - let config = { - prefix: '@]$', - content: [ - { - raw: html` -
-
-
-
-
-
- - -
- `, - }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind utilities; - @layer utilities { - .my-utility { - color: orange; - } - } - .foo { - @apply @]$text-white; - @apply [background-color:red]; - } - ` + const result = await run(input, config) - const result = await run(input, config) + expect(result.css).toMatchFormattedCss(css` + .\@bg-black { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); + } + .\@underline { + text-decoration-line: underline; + } + .my-utility { + color: orange; + } + .foo { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); + background-color: red; + } + .hover\:before\:\@content-\[\'Hovering\'\]:hover:before { + --tw-content: 'Hovering'; + content: var(--tw-content); + } + `) +}) - // TODO: The class `.hover\:before\:\@\]\$content-\[\'Hovering\'\]:hover::before` is not generated - // This happens because of the parenthesis/brace/bracket clipping performed on candidates +test('supports non-word prefixes (2)', async () => { + let config = { + prefix: '@]$', + content: [ + { + raw: html` +
+
+
+
+
+
+ + +
+ `, + }, + ], + corePlugins: { preflight: false }, + } - expect(result.css).toMatchFormattedCss(css` - .\@\]\$bg-black { - --tw-bg-opacity: 1; - background-color: rgb(0 0 0 / var(--tw-bg-opacity)); - } - .\@\]\$underline { - text-decoration-line: underline; - } + let input = css` + @tailwind utilities; + @layer utilities { .my-utility { color: orange; } - .foo { - --tw-text-opacity: 1; - color: rgb(255 255 255 / var(--tw-text-opacity)); - background-color: red; - } - `) - }) + } + .foo { + @apply @]$text-white; + @apply [background-color:red]; + } + ` + + const result = await run(input, config) + + // TODO: The class `.hover\:before\:\@\]\$content-\[\'Hovering\'\]:hover::before` is not generated + // This happens because of the parenthesis/brace/bracket clipping performed on candidates + + expect(result.css).toMatchFormattedCss(css` + .\@\]\$bg-black { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); + } + .\@\]\$underline { + text-decoration-line: underline; + } + .my-utility { + color: orange; + } + .foo { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); + background-color: red; + } + `) }) test('does not prefix arbitrary group/peer classes', async () => { diff --git a/tests/prefixSelector.test.js b/tests/prefixSelector.test.js index c7db08d03c56..92fd6194f6de 100644 --- a/tests/prefixSelector.test.js +++ b/tests/prefixSelector.test.js @@ -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' + ) }) diff --git a/tests/preflight.test.js b/tests/preflight.test.js index 4f6f722c706c..7ba5d3f9fa13 100644 --- a/tests/preflight.test.js +++ b/tests/preflight.test.js @@ -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`
` }], - theme: { - borderColor: ({ theme }) => theme('colors'), - }, - plugins: [], - corePlugins: { preflight: true }, - } +it('preflight has a correct border color fallback', () => { + let config = { + content: [{ raw: html`
` }], + 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;`) }) }) diff --git a/tests/raw-content.test.js b/tests/raw-content.test.js index 329c73d13d07..a55e373fb21e 100644 --- a/tests/raw-content.test.js +++ b/tests/raw-content.test.js @@ -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` -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - } +it('raw content', () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ `, + }, + ], + 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') + ) }) }) diff --git a/tests/relative-purge-paths.test.js b/tests/relative-purge-paths.test.js index 95fbe7bad67c..0e7e5feed03a 100644 --- a/tests/relative-purge-paths.test.js +++ b/tests/relative-purge-paths.test.js @@ -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; + } + `) }) }) diff --git a/tests/resolve-defaults-at-rules.test.js b/tests/resolve-defaults-at-rules.test.js index e8964c4c6f7e..72e1bfb6e586 100644 --- a/tests/resolve-defaults-at-rules.test.js +++ b/tests/resolve-defaults-at-rules.test.js @@ -1,775 +1,772 @@ -import { crosscheck, run, html, css } from './util/run' +import { run, html, css } from './util/run' -crosscheck(() => { - test('basic utilities', async () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: ['transform', 'scale', 'rotate', 'skew'], - } +test('basic utilities', async () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: ['transform', 'scale', 'rotate', 'skew'], + } - let input = css` - @tailwind base; - /* --- */ - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - *, - :before, - :after, - ::backdrop { - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - } - .rotate-3 { - --tw-rotate: 3deg; - 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)); - } - .skew-y-6 { - --tw-skew-y: 6deg; - 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)); - } - .scale-x-110 { - --tw-scale-x: 1.1; - 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)); - } - `) - }) + let input = css` + @tailwind base; + /* --- */ + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + *, + :before, + :after, + ::backdrop { + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + } + .rotate-3 { + --tw-rotate: 3deg; + 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)); + } + .skew-y-6 { + --tw-skew-y: 6deg; + 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)); + } + .scale-x-110 { + --tw-scale-x: 1.1; + 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)); + } + `) }) +}) - test('with pseudo-class variants', async () => { - let config = { - content: [ - { raw: html`
` }, - ], - corePlugins: ['transform', 'scale', 'rotate', 'skew'], - } +test('with pseudo-class variants', async () => { + let config = { + content: [ + { raw: html`
` }, + ], + corePlugins: ['transform', 'scale', 'rotate', 'skew'], + } - let input = css` - @tailwind base; - /* --- */ - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - *, - :before, - :after, - ::backdrop { - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - } - .hover\:scale-x-110:hover { - --tw-scale-x: 1.1; - 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)); - } - .focus\:rotate-3:focus { - --tw-rotate: 3deg; - 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)); - } - .hover\:focus\:skew-y-6:focus:hover { - --tw-skew-y: 6deg; - 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)); - } - `) - }) + let input = css` + @tailwind base; + /* --- */ + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + *, + :before, + :after, + ::backdrop { + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + } + .hover\:scale-x-110:hover { + --tw-scale-x: 1.1; + 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)); + } + .focus\:rotate-3:focus { + --tw-rotate: 3deg; + 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)); + } + .hover\:focus\:skew-y-6:focus:hover { + --tw-skew-y: 6deg; + 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)); + } + `) }) +}) - test('with pseudo-element variants', async () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: ['transform', 'scale', 'rotate', 'skew'], - } +test('with pseudo-element variants', async () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: ['transform', 'scale', 'rotate', 'skew'], + } - let input = css` - @tailwind base; - /* --- */ - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - *, - :before, - :after, - ::backdrop { - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - } - .before\:scale-x-110:before { - content: var(--tw-content); - --tw-scale-x: 1.1; - 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)); - } - .after\:rotate-3:after { - content: var(--tw-content); - --tw-rotate: 3deg; - 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)); - } - `) - }) + let input = css` + @tailwind base; + /* --- */ + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + *, + :before, + :after, + ::backdrop { + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + } + .before\:scale-x-110:before { + content: var(--tw-content); + --tw-scale-x: 1.1; + 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)); + } + .after\:rotate-3:after { + content: var(--tw-content); + --tw-rotate: 3deg; + 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)); + } + `) }) +}) - test('with multi-class variants', async () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: ['transform', 'scale', 'rotate', 'skew'], - } +test('with multi-class variants', async () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: ['transform', 'scale', 'rotate', 'skew'], + } - let input = css` - @tailwind base; - /* --- */ - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - *, - :before, - :after, - ::backdrop { - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - } - .group:hover .group-hover\:scale-x-110 { - --tw-scale-x: 1.1; - 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)); - } - .peer:focus ~ .peer-focus\:rotate-3 { - --tw-rotate: 3deg; - 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)); - } - `) - }) + let input = css` + @tailwind base; + /* --- */ + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + *, + :before, + :after, + ::backdrop { + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + } + .group:hover .group-hover\:scale-x-110 { + --tw-scale-x: 1.1; + 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)); + } + .peer:focus ~ .peer-focus\:rotate-3 { + --tw-rotate: 3deg; + 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)); + } + `) }) +}) - test('with multi-class pseudo-element variants', async () => { - let config = { - content: [ - { raw: html`
` }, - ], - corePlugins: ['transform', 'scale', 'rotate', 'skew'], - } +test('with multi-class pseudo-element variants', async () => { + let config = { + content: [ + { raw: html`
` }, + ], + corePlugins: ['transform', 'scale', 'rotate', 'skew'], + } - let input = css` - @tailwind base; - /* --- */ - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - *, - :before, - :after, - ::backdrop { - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - } - .group:hover .group-hover\:before\:scale-x-110:before { - content: var(--tw-content); - --tw-scale-x: 1.1; - 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)); - } - .peer:focus ~ .peer-focus\:after\:rotate-3:after { - content: var(--tw-content); - --tw-rotate: 3deg; - 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)); - } - `) - }) + let input = css` + @tailwind base; + /* --- */ + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + *, + :before, + :after, + ::backdrop { + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + } + .group:hover .group-hover\:before\:scale-x-110:before { + content: var(--tw-content); + --tw-scale-x: 1.1; + 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)); + } + .peer:focus ~ .peer-focus\:after\:rotate-3:after { + content: var(--tw-content); + --tw-rotate: 3deg; + 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)); + } + `) }) +}) - test('with multi-class pseudo-element and pseudo-class variants', async () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - corePlugins: ['transform', 'scale', 'rotate', 'skew'], - } +test('with multi-class pseudo-element and pseudo-class variants', async () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: ['transform', 'scale', 'rotate', 'skew'], + } - let input = css` - @tailwind base; - /* --- */ - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - *, - :before, - :after, - ::backdrop { - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - } - .group:hover .group-hover\:hover\:before\:scale-x-110:hover:before { - content: var(--tw-content); - --tw-scale-x: 1.1; - 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)); - } - .peer:focus ~ .peer-focus\:focus\:after\:rotate-3:focus:after { - content: var(--tw-content); - --tw-rotate: 3deg; - 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)); - } - `) - }) + let input = css` + @tailwind base; + /* --- */ + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + *, + :before, + :after, + ::backdrop { + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + } + .group:hover .group-hover\:hover\:before\:scale-x-110:hover:before { + content: var(--tw-content); + --tw-scale-x: 1.1; + 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)); + } + .peer:focus ~ .peer-focus\:focus\:after\:rotate-3:focus:after { + content: var(--tw-content); + --tw-rotate: 3deg; + 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)); + } + `) }) +}) - test('with apply', async () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: ['transform', 'scale', 'rotate', 'skew'], - } +test('with apply', async () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: ['transform', 'scale', 'rotate', 'skew'], + } - let input = css` - @tailwind base; - /* --- */ - @tailwind utilities; + let input = css` + @tailwind base; + /* --- */ + @tailwind utilities; - @layer utilities { - .foo { - @apply rotate-3; - } + @layer utilities { + .foo { + @apply rotate-3; } + } - .bar { - @apply before:scale-110; - } + .bar { + @apply before:scale-110; + } - .baz::before { - @apply rotate-45; - } + .baz::before { + @apply rotate-45; + } + + .whats ~ .next > span:hover { + @apply skew-x-6; + } + + .media-queries { + @apply md:rotate-45; + } + + .a, + .b, + .c { + @apply skew-y-3; + } + + .a, + .b { + @apply rotate-45; + } + .a::before, + .b::after { + @apply rotate-90; + } + + .recursive { + @apply foo; + } + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + *, + :before, + :after, + ::backdrop { + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + } + .foo { + --tw-rotate: 3deg; + 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)); + } + .bar:before { + content: var(--tw-content); + --tw-scale-x: 1.1; + --tw-scale-y: 1.1; + 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)); + } + .baz:before { + --tw-rotate: 45deg; + 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)); + } .whats ~ .next > span:hover { - @apply skew-x-6; + --tw-skew-x: 6deg; + 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)); } - - .media-queries { - @apply md:rotate-45; + @media (min-width: 768px) { + .media-queries { + --tw-rotate: 45deg; + 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)); + } } - .a, .b, .c { - @apply skew-y-3; + --tw-skew-y: 3deg; + 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)); } - .a, .b { - @apply rotate-45; + --tw-rotate: 45deg; + 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)); } - - .a::before, - .b::after { - @apply rotate-90; + .a:before, + .b:after { + --tw-rotate: 90deg; + 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)); } - .recursive { - @apply foo; - } - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - *, - :before, - :after, - ::backdrop { - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - } - .foo { - --tw-rotate: 3deg; - 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)); - } - .bar:before { - content: var(--tw-content); - --tw-scale-x: 1.1; - --tw-scale-y: 1.1; - 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)); - } - .baz:before { - --tw-rotate: 45deg; - 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)); - } - .whats ~ .next > span:hover { - --tw-skew-x: 6deg; - 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)); - } - @media (min-width: 768px) { - .media-queries { - --tw-rotate: 45deg; - 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)); - } - } - .a, - .b, - .c { - --tw-skew-y: 3deg; - 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)); - } - .a, - .b { - --tw-rotate: 45deg; - 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)); - } - .a:before, - .b:after { - --tw-rotate: 90deg; - 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)); - } - .recursive { - --tw-rotate: 3deg; - 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)); - } - `) - }) + --tw-rotate: 3deg; + 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)); + } + `) }) +}) + +test('legacy pseudo-element syntax is supported', async () => { + let config = { + experimental: { optimizeUniversalDefaults: true }, + content: [{ raw: html`
` }], + corePlugins: ['transform', 'scale', 'rotate', 'skew'], + } + + let input = css` + @tailwind base; + /* --- */ + @tailwind utilities; + + .a:before { + @apply rotate-45; + } - test('legacy pseudo-element syntax is supported', async () => { - let config = { - experimental: { optimizeUniversalDefaults: true }, - content: [{ raw: html`
` }], - corePlugins: ['transform', 'scale', 'rotate', 'skew'], + .b:after { + @apply rotate-3; } - let input = css` - @tailwind base; - /* --- */ - @tailwind utilities; + .c:first-line { + @apply rotate-1; + } + + .d:first-letter { + @apply rotate-6; + } + ` + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .a:before, + .b:after, + .c:first-line, + .d:first-letter { + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + } .a:before { - @apply rotate-45; + --tw-rotate: 45deg; + 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)); } - .b:after { - @apply rotate-3; + --tw-rotate: 3deg; + 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)); } - .c:first-line { - @apply rotate-1; + --tw-rotate: 1deg; + 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)); } - .d:first-letter { - @apply rotate-6; - } - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .a:before, - .b:after, - .c:first-line, - .d:first-letter { - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - } - .a:before { - --tw-rotate: 45deg; - 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)); - } - .b:after { - --tw-rotate: 3deg; - 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)); - } - .c:first-line { - --tw-rotate: 1deg; - 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)); - } - .d:first-letter { - --tw-rotate: 6deg; - 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)); - } - `) - }) + --tw-rotate: 6deg; + 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)); + } + `) }) +}) - test('with borders', async () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: ['borderWidth', 'borderColor', 'borderOpacity'], - } +test('with borders', async () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: ['borderWidth', 'borderColor', 'borderOpacity'], + } - let input = css` - @tailwind base; - /* --- */ - @tailwind utilities; - ` + let input = css` + @tailwind base; + /* --- */ + @tailwind utilities; + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .border { - border-width: 1px; - } - .border-red-500 { - --tw-border-opacity: 1; - border-color: rgb(239 68 68 / var(--tw-border-opacity)); - } - @media (min-width: 768px) { - .md\:border-2 { - border-width: 2px; - } + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .border { + border-width: 1px; + } + .border-red-500 { + --tw-border-opacity: 1; + border-color: rgb(239 68 68 / var(--tw-border-opacity)); + } + @media (min-width: 768px) { + .md\:border-2 { + border-width: 2px; } - `) - }) + } + `) }) +}) - test('with shadows', async () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: ['boxShadow', 'ringColor', 'ringWidth'], - } +test('with shadows', async () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: ['boxShadow', 'ringColor', 'ringWidth'], + } - let input = css` - @tailwind base; - /* --- */ - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - *, - :before, - :after, - ::backdrop { - --tw-ring-inset: ; - --tw-ring-offset-width: 0px; - --tw-ring-offset-color: #fff; - --tw-ring-color: #3b82f680; - --tw-ring-offset-shadow: 0 0 #0000; - --tw-ring-shadow: 0 0 #0000; - --tw-shadow: 0 0 #0000; - --tw-shadow-colored: 0 0 #0000; - } - .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); + let input = css` + @tailwind base; + /* --- */ + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + *, + :before, + :after, + ::backdrop { + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: #3b82f680; + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + } + .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); + } + .ring-1 { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) + var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) + var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); + } + .ring-black\/25 { + --tw-ring-color: #00000040; + } + @media (min-width: 768px) { + .md\:shadow-xl { + --tw-shadow: 0 20px 25px -5px #0000001a, 0 8px 10px -6px #0000001a; + --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), + 0 8px 10px -6px var(--tw-shadow-color); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } - .ring-1 { - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) - var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) - var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), - var(--tw-shadow, 0 0 #0000); - } - .ring-black\/25 { - --tw-ring-color: #00000040; - } - @media (min-width: 768px) { - .md\:shadow-xl { - --tw-shadow: 0 20px 25px -5px #0000001a, 0 8px 10px -6px #0000001a; - --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), - 0 8px 10px -6px 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('when no utilities that need the defaults are used', async () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: ['transform', 'scale', 'rotate', 'skew'], - } +test('when no utilities that need the defaults are used', async () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: ['transform', 'scale', 'rotate', 'skew'], + } - let input = css` - @tailwind base; - /* --- */ - @tailwind utilities; - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - *, - :before, - :after, - ::backdrop { - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - } - `) - }) + let input = css` + @tailwind base; + /* --- */ + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + *, + :before, + :after, + ::backdrop { + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + } + `) }) +}) - test('when a utility uses defaults but they do not exist', async () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: ['rotate'], - } +test('when a utility uses defaults but they do not exist', async () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: ['rotate'], + } - let input = css` - @tailwind base; - /* --- */ - @tailwind utilities; - ` + let input = css` + @tailwind base; + /* --- */ + @tailwind utilities; + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .rotate-3 { - --tw-rotate: 3deg; - 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)); - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .rotate-3 { + --tw-rotate: 3deg; + 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)); + } + `) }) +}) + +test('selectors are reduced to the lowest possible specificity', async () => { + let config = { + experimental: { optimizeUniversalDefaults: true }, + content: [{ raw: html`
` }], + corePlugins: [], + } - test('selectors are reduced to the lowest possible specificity', async () => { - let config = { - experimental: { optimizeUniversalDefaults: true }, - content: [{ raw: html`
` }], - corePlugins: [], + let input = css` + @defaults test { + --color: black; } - let input = css` - @defaults test { - --color: black; - } + /* --- */ - /* --- */ + .foo { + @defaults test; + background-color: var(--color); + } + #app { + @defaults test; + border-color: var(--color); + } + + span#page { + @defaults test; + color: var(--color); + } + + div[data-foo='bar']#other { + @defaults test; + fill: var(--color); + } + + div[data-bar='baz'] { + @defaults test; + stroke: var(--color); + } + + article { + @defaults test; + --article: var(--color); + } + + div[data-foo='bar']#another::before { + @defaults test; + fill: var(--color); + } + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .foo, + [id='app'], + [id='page'], + [id='other'], + [data-bar='baz'], + article, + [id='another']:before { + --color: black; + } .foo { - @defaults test; background-color: var(--color); } - #app { - @defaults test; border-color: var(--color); } - span#page { - @defaults test; color: var(--color); } - div[data-foo='bar']#other { - @defaults test; fill: var(--color); } - div[data-bar='baz'] { - @defaults test; stroke: var(--color); } - article { - @defaults test; --article: var(--color); } - - div[data-foo='bar']#another::before { - @defaults test; + div[data-foo='bar']#another:before { fill: var(--color); } - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .foo, - [id='app'], - [id='page'], - [id='other'], - [data-bar='baz'], - article, - [id='another']:before { - --color: black; - } - .foo { - background-color: var(--color); - } - #app { - border-color: var(--color); - } - span#page { - color: var(--color); - } - div[data-foo='bar']#other { - fill: var(--color); - } - div[data-bar='baz'] { - stroke: var(--color); - } - article { - --article: var(--color); - } - div[data-foo='bar']#another:before { - fill: var(--color); - } - `) - }) + `) }) +}) - test('No defaults without @tailwind base', () => { - let config = { - experimental: { optimizeUniversalDefaults: true }, - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } +test('No defaults without @tailwind base', () => { + let config = { + experimental: { optimizeUniversalDefaults: true }, + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } - // Optimize universal defaults doesn't work well with isolated modules - // We require you to use @tailwind base to inject the defaults - let input = css` - @tailwind components; - @tailwind utilities; - ` - - return run(input, config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .scale-150 { - --tw-scale-x: 1.5; - --tw-scale-y: 1.5; - 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)); - } - `) - }) + // Optimize universal defaults doesn't work well with isolated modules + // We require you to use @tailwind base to inject the defaults + let input = css` + @tailwind components; + @tailwind utilities; + ` + + return run(input, config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .scale-150 { + --tw-scale-x: 1.5; + --tw-scale-y: 1.5; + 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)); + } + `) }) +}) - test('no defaults and apply without @tailwind base', () => { - let config = { - experimental: { optimizeUniversalDefaults: true }, - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } +test('no defaults and apply without @tailwind base', () => { + let config = { + experimental: { optimizeUniversalDefaults: true }, + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } - // Optimize universal defaults doesn't work well with isolated modules - // We require you to use @tailwind base to inject the defaults - let input = css` - @tailwind components; - @tailwind utilities; + // Optimize universal defaults doesn't work well with isolated modules + // We require you to use @tailwind base to inject the defaults + let input = css` + @tailwind components; + @tailwind utilities; + .my-card { + @apply scale-150; + } + ` + + return run(input, config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` .my-card { - @apply scale-150; + --tw-scale-x: 1.5; + --tw-scale-y: 1.5; + 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)); } - ` - - return run(input, config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .my-card { - --tw-scale-x: 1.5; - --tw-scale-y: 1.5; - 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)); - } - `) - }) + `) }) }) diff --git a/tests/resolveConfig.test.js b/tests/resolveConfig.test.js index 69a62bba5529..e086986326e5 100644 --- a/tests/resolveConfig.test.js +++ b/tests/resolveConfig.test.js @@ -1,1797 +1,1794 @@ import resolveConfig from '../src/util/resolveConfig' import corePluginList from '../src/corePluginList' -import { crosscheck } from './util/run' - -crosscheck(() => { - test('prefix key overrides default prefix', () => { - const userConfig = { - prefix: 'tw-', - } - - const defaultConfig = { - prefix: '', - important: false, - separator: ':', - content: [], - theme: { - screens: { - mobile: '400px', - }, - }, - } - const result = resolveConfig([userConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: 'tw-', - important: false, - separator: ':', - theme: { - screens: { - mobile: '400px', - }, - }, - }) +test('prefix key overrides default prefix', () => { + const userConfig = { + prefix: 'tw-', + } + + const defaultConfig = { + prefix: '', + important: false, + separator: ':', + content: [], + theme: { + screens: { + mobile: '400px', + }, + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: 'tw-', + important: false, + separator: ':', + theme: { + screens: { + mobile: '400px', + }, + }, }) +}) - test('important key overrides default important', () => { - const userConfig = { - important: true, - } - - const defaultConfig = { - prefix: '', - important: false, - separator: ':', - content: [], - theme: { - screens: { - mobile: '400px', - }, - }, - } - - const result = resolveConfig([userConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: '', - important: true, - separator: ':', - theme: { - screens: { - mobile: '400px', - }, - }, - }) +test('important key overrides default important', () => { + const userConfig = { + important: true, + } + + const defaultConfig = { + prefix: '', + important: false, + separator: ':', + content: [], + theme: { + screens: { + mobile: '400px', + }, + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '', + important: true, + separator: ':', + theme: { + screens: { + mobile: '400px', + }, + }, }) +}) - test('important (selector) key overrides default important', () => { - const userConfig = { - important: '#app', - } - - const defaultConfig = { - prefix: '', - important: false, - separator: ':', - content: [], - theme: { - screens: { - mobile: '400px', - }, - }, - } - - const result = resolveConfig([userConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: '', - important: '#app', - separator: ':', - theme: { - screens: { - mobile: '400px', - }, - }, - }) +test('important (selector) key overrides default important', () => { + const userConfig = { + important: '#app', + } + + const defaultConfig = { + prefix: '', + important: false, + separator: ':', + content: [], + theme: { + screens: { + mobile: '400px', + }, + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '', + important: '#app', + separator: ':', + theme: { + screens: { + mobile: '400px', + }, + }, }) +}) - test('separator key overrides default separator', () => { - const userConfig = { - separator: '__', - } - - const defaultConfig = { - prefix: '', - important: false, - separator: ':', - content: [], - theme: { - screens: { - mobile: '400px', - }, - }, - } - - const result = resolveConfig([userConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: '', - important: false, - separator: '__', - theme: { - screens: { - mobile: '400px', - }, - }, - }) +test('separator key overrides default separator', () => { + const userConfig = { + separator: '__', + } + + const defaultConfig = { + prefix: '', + important: false, + separator: ':', + content: [], + theme: { + screens: { + mobile: '400px', + }, + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '', + important: false, + separator: '__', + theme: { + screens: { + mobile: '400px', + }, + }, }) +}) - test('theme key is merged instead of replaced', () => { - const userConfig = { - theme: { - screens: { - mobile: '400px', - }, - }, - } - - const defaultConfig = { - prefix: '-', - important: false, - separator: ':', - content: [], - theme: { - colors: { - 'grey-darker': '#606f7b', - 'grey-dark': '#8795a1', - grey: '#b8c2cc', - 'grey-light': '#dae1e7', - 'grey-lighter': '#f1f5f8', - }, - fonts: { - sans: ['system-ui', 'Roboto', 'sans-serif'], - serif: ['Constantia', 'Lucida Bright', 'Georgia', 'serif'], - }, - screens: { - sm: '500px', - md: '750px', - lg: '1000px', - }, - }, - } - - const result = resolveConfig([userConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: '-', - important: false, - separator: ':', - theme: { - colors: { - 'grey-darker': '#606f7b', - 'grey-dark': '#8795a1', - grey: '#b8c2cc', - 'grey-light': '#dae1e7', - 'grey-lighter': '#f1f5f8', - }, - fonts: { - sans: ['system-ui', 'Roboto', 'sans-serif'], - serif: ['Constantia', 'Lucida Bright', 'Georgia', 'serif'], - }, - screens: { - mobile: '400px', - }, - }, - }) +test('theme key is merged instead of replaced', () => { + const userConfig = { + theme: { + screens: { + mobile: '400px', + }, + }, + } + + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + content: [], + theme: { + colors: { + 'grey-darker': '#606f7b', + 'grey-dark': '#8795a1', + grey: '#b8c2cc', + 'grey-light': '#dae1e7', + 'grey-lighter': '#f1f5f8', + }, + fonts: { + sans: ['system-ui', 'Roboto', 'sans-serif'], + serif: ['Constantia', 'Lucida Bright', 'Georgia', 'serif'], + }, + screens: { + sm: '500px', + md: '750px', + lg: '1000px', + }, + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '-', + important: false, + separator: ':', + theme: { + colors: { + 'grey-darker': '#606f7b', + 'grey-dark': '#8795a1', + grey: '#b8c2cc', + 'grey-light': '#dae1e7', + 'grey-lighter': '#f1f5f8', + }, + fonts: { + sans: ['system-ui', 'Roboto', 'sans-serif'], + serif: ['Constantia', 'Lucida Bright', 'Georgia', 'serif'], + }, + screens: { + mobile: '400px', + }, + }, }) +}) - test('theme key is deeply merged instead of replaced', () => { - const userConfig = { - theme: { - extend: { - colors: { - grey: { - darker: '#606f7b', - dark: '#8795a1', - }, - }, - }, - }, - } - - const defaultConfig = { - prefix: '-', - important: false, - separator: ':', - content: [], - theme: { - colors: { - grey: { - grey: '#b8c2cc', - light: '#dae1e7', - lighter: '#f1f5f8', - }, - }, - }, - } - - const result = resolveConfig([userConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: '-', - important: false, - separator: ':', - theme: { +test('theme key is deeply merged instead of replaced', () => { + const userConfig = { + theme: { + extend: { colors: { grey: { darker: '#606f7b', dark: '#8795a1', - grey: '#b8c2cc', - light: '#dae1e7', - lighter: '#f1f5f8', }, }, }, - }) - }) + }, + } - test('missing top level keys are pulled from the default config', () => { - const userConfig = {} - - const defaultConfig = { - prefix: '-', - important: false, - separator: ':', - content: [], - theme: { - colors: { green: '#00ff00' }, - screens: { - mobile: '400px', + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + content: [], + theme: { + colors: { + grey: { + grey: '#b8c2cc', + light: '#dae1e7', + lighter: '#f1f5f8', }, }, - } + }, + } - const result = resolveConfig([userConfig, defaultConfig]) + const result = resolveConfig([userConfig, defaultConfig]) - expect(result).toMatchObject({ - prefix: '-', - important: false, - separator: ':', - theme: { - colors: { green: '#00ff00' }, - screens: { - mobile: '400px', + expect(result).toMatchObject({ + prefix: '-', + important: false, + separator: ':', + theme: { + colors: { + grey: { + darker: '#606f7b', + dark: '#8795a1', + grey: '#b8c2cc', + light: '#dae1e7', + lighter: '#f1f5f8', }, }, - }) + }, }) +}) - test('functions in the default theme section are lazily evaluated', () => { - const userConfig = { - theme: { - colors: { - red: 'red', - green: 'green', - blue: 'blue', - }, - }, - } - - const defaultConfig = { - prefix: '-', - important: false, - separator: ':', - content: [], - theme: { - colors: { - cyan: 'cyan', - magenta: 'magenta', - yellow: 'yellow', - }, - backgroundColors: (theme) => theme('colors'), - textColors: (theme) => theme('colors'), - }, - } - - const result = resolveConfig([userConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: '-', - important: false, - separator: ':', - theme: { - colors: { - red: 'red', - green: 'green', - blue: 'blue', - }, - backgroundColors: { - red: 'red', - green: 'green', - blue: 'blue', - }, - textColors: { - red: 'red', - green: 'green', - blue: 'blue', - }, - }, - }) +test('missing top level keys are pulled from the default config', () => { + const userConfig = {} + + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + content: [], + theme: { + colors: { green: '#00ff00' }, + screens: { + mobile: '400px', + }, + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '-', + important: false, + separator: ':', + theme: { + colors: { green: '#00ff00' }, + screens: { + mobile: '400px', + }, + }, }) +}) - test('functions in the user theme section are lazily evaluated', () => { - const userConfig = { - theme: { - colors: { - red: 'red', - green: 'green', - blue: 'blue', - }, - backgroundColors: (theme) => ({ - ...theme('colors'), - customBackground: '#bada55', - }), - textColors: (theme) => ({ - ...theme('colors'), - customText: '#facade', - }), - }, - } - - const defaultConfig = { - prefix: '-', - important: false, - separator: ':', - content: [], - theme: { - colors: { - cyan: 'cyan', - magenta: 'magenta', - yellow: 'yellow', - }, - backgroundColors: ({ colors }) => colors, - textColors: ({ colors }) => colors, - }, - } - - const result = resolveConfig([userConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: '-', - important: false, - separator: ':', - theme: { - colors: { - red: 'red', - green: 'green', - blue: 'blue', - }, - backgroundColors: { - red: 'red', - green: 'green', - blue: 'blue', - customBackground: '#bada55', - }, - textColors: { - red: 'red', - green: 'green', - blue: 'blue', - customText: '#facade', - }, - }, - }) +test('functions in the default theme section are lazily evaluated', () => { + const userConfig = { + theme: { + colors: { + red: 'red', + green: 'green', + blue: 'blue', + }, + }, + } + + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + content: [], + theme: { + colors: { + cyan: 'cyan', + magenta: 'magenta', + yellow: 'yellow', + }, + backgroundColors: (theme) => theme('colors'), + textColors: (theme) => theme('colors'), + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '-', + important: false, + separator: ':', + theme: { + colors: { + red: 'red', + green: 'green', + blue: 'blue', + }, + backgroundColors: { + red: 'red', + green: 'green', + blue: 'blue', + }, + textColors: { + red: 'red', + green: 'green', + blue: 'blue', + }, + }, }) +}) - test('theme values in the extend section extend the existing theme', () => { - const userConfig = { - theme: { - extend: { - opacity: { - 25: '25', - 75: '.75', - }, - backgroundColors: { - customBackground: '#bada55', - }, - }, - }, - } - - const defaultConfig = { - prefix: '-', - important: false, - separator: ':', - content: [], - theme: { - colors: { - cyan: 'cyan', - magenta: 'magenta', - yellow: 'yellow', - }, - opacity: { - 0: '0', - 50: '.5', - 100: '1', - }, - backgroundColors: (theme) => theme('colors'), - }, - } - - const result = resolveConfig([userConfig, defaultConfig]) +test('functions in the user theme section are lazily evaluated', () => { + const userConfig = { + theme: { + colors: { + red: 'red', + green: 'green', + blue: 'blue', + }, + backgroundColors: (theme) => ({ + ...theme('colors'), + customBackground: '#bada55', + }), + textColors: (theme) => ({ + ...theme('colors'), + customText: '#facade', + }), + }, + } + + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + content: [], + theme: { + colors: { + cyan: 'cyan', + magenta: 'magenta', + yellow: 'yellow', + }, + backgroundColors: ({ colors }) => colors, + textColors: ({ colors }) => colors, + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '-', + important: false, + separator: ':', + theme: { + colors: { + red: 'red', + green: 'green', + blue: 'blue', + }, + backgroundColors: { + red: 'red', + green: 'green', + blue: 'blue', + customBackground: '#bada55', + }, + textColors: { + red: 'red', + green: 'green', + blue: 'blue', + customText: '#facade', + }, + }, + }) +}) - expect(result).toMatchObject({ - prefix: '-', - important: false, - separator: ':', - theme: { - colors: { - cyan: 'cyan', - magenta: 'magenta', - yellow: 'yellow', - }, +test('theme values in the extend section extend the existing theme', () => { + const userConfig = { + theme: { + extend: { opacity: { - 0: '0', - 50: '.5', - 100: '1', 25: '25', 75: '.75', }, backgroundColors: { - cyan: 'cyan', - magenta: 'magenta', - yellow: 'yellow', customBackground: '#bada55', }, }, - }) + }, + } + + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + content: [], + theme: { + colors: { + cyan: 'cyan', + magenta: 'magenta', + yellow: 'yellow', + }, + opacity: { + 0: '0', + 50: '.5', + 100: '1', + }, + backgroundColors: (theme) => theme('colors'), + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '-', + important: false, + separator: ':', + theme: { + colors: { + cyan: 'cyan', + magenta: 'magenta', + yellow: 'yellow', + }, + opacity: { + 0: '0', + 50: '.5', + 100: '1', + 25: '25', + 75: '.75', + }, + backgroundColors: { + cyan: 'cyan', + magenta: 'magenta', + yellow: 'yellow', + customBackground: '#bada55', + }, + }, }) +}) - test('theme values in the extend section extend the user theme', () => { - const userConfig = { - theme: { - opacity: { - 0: '0', - 20: '.2', - 40: '.4', - }, - height: (theme) => theme('width'), - extend: { - opacity: { - 60: '.6', - 80: '.8', - 100: '1', - }, - height: { - customHeight: '500vh', - }, - }, - }, - } - - const defaultConfig = { - prefix: '-', - important: false, - separator: ':', - content: [], - theme: { - opacity: { - 0: '0', - 50: '.5', - 100: '1', - }, - height: { - 0: 0, - full: '100%', - }, - width: { - 0: 0, - 1: '.25rem', - 2: '.5rem', - 3: '.75rem', - 4: '1rem', - }, +test('theme values in the extend section extend the user theme', () => { + const userConfig = { + theme: { + opacity: { + 0: '0', + 20: '.2', + 40: '.4', }, - } - - const result = resolveConfig([userConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: '-', - important: false, - separator: ':', - theme: { + height: (theme) => theme('width'), + extend: { opacity: { - 0: '0', - 20: '.2', - 40: '.4', 60: '.6', 80: '.8', 100: '1', }, height: { - 0: 0, - 1: '.25rem', - 2: '.5rem', - 3: '.75rem', - 4: '1rem', customHeight: '500vh', }, - width: { - 0: 0, - 1: '.25rem', - 2: '.5rem', - 3: '.75rem', - 4: '1rem', - }, }, - }) + }, + } + + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + content: [], + theme: { + opacity: { + 0: '0', + 50: '.5', + 100: '1', + }, + height: { + 0: 0, + full: '100%', + }, + width: { + 0: 0, + 1: '.25rem', + 2: '.5rem', + 3: '.75rem', + 4: '1rem', + }, + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '-', + important: false, + separator: ':', + theme: { + opacity: { + 0: '0', + 20: '.2', + 40: '.4', + 60: '.6', + 80: '.8', + 100: '1', + }, + height: { + 0: 0, + 1: '.25rem', + 2: '.5rem', + 3: '.75rem', + 4: '1rem', + customHeight: '500vh', + }, + width: { + 0: 0, + 1: '.25rem', + 2: '.5rem', + 3: '.75rem', + 4: '1rem', + }, + }, }) +}) - test('theme values in the extend section can extend values that are depended on lazily', () => { - const userConfig = { - theme: { - extend: { - colors: { - red: 'red', - green: 'green', - blue: 'blue', - }, - backgroundColors: { - customBackground: '#bada55', - }, - }, - }, - } - - const defaultConfig = { - prefix: '-', - important: false, - separator: ':', - content: [], - theme: { - colors: { - cyan: 'cyan', - magenta: 'magenta', - yellow: 'yellow', - }, - backgroundColors: (theme) => theme('colors'), - }, - } - - const result = resolveConfig([userConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: '-', - important: false, - separator: ':', - theme: { +test('theme values in the extend section can extend values that are depended on lazily', () => { + const userConfig = { + theme: { + extend: { colors: { - cyan: 'cyan', - magenta: 'magenta', - yellow: 'yellow', red: 'red', green: 'green', blue: 'blue', }, backgroundColors: { - cyan: 'cyan', - magenta: 'magenta', - yellow: 'yellow', - red: 'red', - green: 'green', - blue: 'blue', customBackground: '#bada55', }, }, - }) + }, + } + + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + content: [], + theme: { + colors: { + cyan: 'cyan', + magenta: 'magenta', + yellow: 'yellow', + }, + backgroundColors: (theme) => theme('colors'), + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '-', + important: false, + separator: ':', + theme: { + colors: { + cyan: 'cyan', + magenta: 'magenta', + yellow: 'yellow', + red: 'red', + green: 'green', + blue: 'blue', + }, + backgroundColors: { + cyan: 'cyan', + magenta: 'magenta', + yellow: 'yellow', + red: 'red', + green: 'green', + blue: 'blue', + customBackground: '#bada55', + }, + }, }) +}) - test('theme values in the extend section are not deeply merged when they are simple arrays', () => { - const userConfig = { - theme: { - extend: { - fonts: { - sans: ['Comic Sans'], - serif: ['Papyrus', { fontFeatureSettings: '"cv11"' }], - mono: [['Lobster', 'Papyrus'], { fontFeatureSettings: '"cv11"' }], - }, - }, - }, - } - - const defaultConfig = { - prefix: '-', - important: false, - separator: ':', - content: [], - theme: { - fonts: { - sans: ['system-ui', 'Helvetica Neue', 'sans-serif'], - serif: ['Constantia', 'Georgia', 'serif'], - mono: ['Menlo', 'Courier New', 'monospace'], - }, - }, - } - - const result = resolveConfig([userConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: '-', - important: false, - separator: ':', - theme: { +test('theme values in the extend section are not deeply merged when they are simple arrays', () => { + const userConfig = { + theme: { + extend: { fonts: { sans: ['Comic Sans'], serif: ['Papyrus', { fontFeatureSettings: '"cv11"' }], mono: [['Lobster', 'Papyrus'], { fontFeatureSettings: '"cv11"' }], }, }, - }) + }, + } + + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + content: [], + theme: { + fonts: { + sans: ['system-ui', 'Helvetica Neue', 'sans-serif'], + serif: ['Constantia', 'Georgia', 'serif'], + mono: ['Menlo', 'Courier New', 'monospace'], + }, + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '-', + important: false, + separator: ':', + theme: { + fonts: { + sans: ['Comic Sans'], + serif: ['Papyrus', { fontFeatureSettings: '"cv11"' }], + mono: [['Lobster', 'Papyrus'], { fontFeatureSettings: '"cv11"' }], + }, + }, }) +}) - test('theme values in the extend section are deeply merged, when they are arrays of objects', () => { - const userConfig = { - theme: { - extend: { - typography: { - ArrayArray: { - css: [{ a: { backgroundColor: 'red' } }, { a: { color: 'green' } }], - }, - ObjectArray: { - css: { a: { backgroundColor: 'red' } }, - }, - ArrayObject: { - css: [{ a: { backgroundColor: 'red' } }, { a: { color: 'green' } }], - }, - }, - }, - }, - } - - const defaultConfig = { - prefix: '-', - important: false, - separator: ':', - content: [], - theme: { - typography: { - ArrayArray: { - css: [{ a: { underline: 'none' } }], - }, - ObjectArray: { - css: [{ a: { underline: 'none' } }], - }, - ArrayObject: { - css: { a: { underline: 'none' } }, - }, - }, - }, - } - - const result = resolveConfig([userConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: '-', - important: false, - separator: ':', - theme: { +test('theme values in the extend section are deeply merged, when they are arrays of objects', () => { + const userConfig = { + theme: { + extend: { typography: { ArrayArray: { - css: [ - { a: { underline: 'none' } }, - { a: { backgroundColor: 'red' } }, - { a: { color: 'green' } }, - ], + css: [{ a: { backgroundColor: 'red' } }, { a: { color: 'green' } }], }, ObjectArray: { - css: [{ a: { underline: 'none' } }, { a: { backgroundColor: 'red' } }], + css: { a: { backgroundColor: 'red' } }, }, ArrayObject: { - css: [ - { a: { underline: 'none' } }, - { a: { backgroundColor: 'red' } }, - { a: { color: 'green' } }, - ], + css: [{ a: { backgroundColor: 'red' } }, { a: { color: 'green' } }], }, }, }, - }) - }) - - test('the theme function can use a default value if the key is missing', () => { - const userConfig = { - theme: { - colors: { - red: 'red', - green: 'green', - blue: 'blue', - }, - }, - } - - const defaultConfig = { - prefix: '-', - important: false, - separator: ':', - content: [], - theme: { - colors: { - cyan: 'cyan', - magenta: 'magenta', - yellow: 'yellow', - }, - borderColor: (theme) => ({ - default: theme('colors.gray', 'currentColor'), - ...theme('colors'), - }), - }, - } - - const result = resolveConfig([userConfig, defaultConfig]) + }, + } - expect(result).toMatchObject({ - prefix: '-', - important: false, - separator: ':', - theme: { - colors: { - red: 'red', - green: 'green', - blue: 'blue', + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + content: [], + theme: { + typography: { + ArrayArray: { + css: [{ a: { underline: 'none' } }], }, - borderColor: { - default: 'currentColor', - red: 'red', - green: 'green', - blue: 'blue', + ObjectArray: { + css: [{ a: { underline: 'none' } }], }, - }, - }) - }) - - test('the theme function can resolve function values', () => { - const userConfig = { - theme: { - textColor: (theme) => ({ - lime: 'lime', - ...theme('colors'), - }), - backgroundColor: (theme) => ({ - orange: 'orange', - ...theme('textColor'), - }), - borderColor: (theme) => theme('backgroundColor'), - }, - } - - const defaultConfig = { - prefix: '-', - important: false, - separator: ':', - content: [], - theme: { - colors: { - red: 'red', - green: 'green', - blue: 'blue', + ArrayObject: { + css: { a: { underline: 'none' } }, }, }, - } + }, + } - const result = resolveConfig([userConfig, defaultConfig]) + const result = resolveConfig([userConfig, defaultConfig]) - expect(result).toMatchObject({ - prefix: '-', - important: false, - separator: ':', - theme: { - colors: { - red: 'red', - green: 'green', - blue: 'blue', + expect(result).toMatchObject({ + prefix: '-', + important: false, + separator: ':', + theme: { + typography: { + ArrayArray: { + css: [ + { a: { underline: 'none' } }, + { a: { backgroundColor: 'red' } }, + { a: { color: 'green' } }, + ], }, - textColor: { - lime: 'lime', - red: 'red', - green: 'green', - blue: 'blue', - }, - backgroundColor: { - lime: 'lime', - orange: 'orange', - red: 'red', - green: 'green', - blue: 'blue', + ObjectArray: { + css: [{ a: { underline: 'none' } }, { a: { backgroundColor: 'red' } }], }, - borderColor: { - lime: 'lime', - orange: 'orange', - red: 'red', - green: 'green', - blue: 'blue', + ArrayObject: { + css: [ + { a: { underline: 'none' } }, + { a: { backgroundColor: 'red' } }, + { a: { color: 'green' } }, + ], }, }, - }) + }, }) +}) - test('the theme function can resolve deep function values', () => { - const userConfig = { - theme: { - minWidth: (theme) => ({ - '1/3': theme('width.1/3'), - }), - }, - } - - const defaultConfig = { - prefix: '-', - important: false, - separator: ':', - content: [], - theme: { - spacing: { - 0: '0', - }, - width: (theme) => ({ - ...theme('spacing'), - '1/3': '33.33333%', - }), - }, - } +test('the theme function can use a default value if the key is missing', () => { + const userConfig = { + theme: { + colors: { + red: 'red', + green: 'green', + blue: 'blue', + }, + }, + } + + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + content: [], + theme: { + colors: { + cyan: 'cyan', + magenta: 'magenta', + yellow: 'yellow', + }, + borderColor: (theme) => ({ + default: theme('colors.gray', 'currentColor'), + ...theme('colors'), + }), + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '-', + important: false, + separator: ':', + theme: { + colors: { + red: 'red', + green: 'green', + blue: 'blue', + }, + borderColor: { + default: 'currentColor', + red: 'red', + green: 'green', + blue: 'blue', + }, + }, + }) +}) - const result = resolveConfig([userConfig, defaultConfig]) +test('the theme function can resolve function values', () => { + const userConfig = { + theme: { + textColor: (theme) => ({ + lime: 'lime', + ...theme('colors'), + }), + backgroundColor: (theme) => ({ + orange: 'orange', + ...theme('textColor'), + }), + borderColor: (theme) => theme('backgroundColor'), + }, + } + + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + content: [], + theme: { + colors: { + red: 'red', + green: 'green', + blue: 'blue', + }, + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '-', + important: false, + separator: ':', + theme: { + colors: { + red: 'red', + green: 'green', + blue: 'blue', + }, + textColor: { + lime: 'lime', + red: 'red', + green: 'green', + blue: 'blue', + }, + backgroundColor: { + lime: 'lime', + orange: 'orange', + red: 'red', + green: 'green', + blue: 'blue', + }, + borderColor: { + lime: 'lime', + orange: 'orange', + red: 'red', + green: 'green', + blue: 'blue', + }, + }, + }) +}) - expect(result).toMatchObject({ - prefix: '-', - important: false, - separator: ':', - theme: { - spacing: { - 0: '0', - }, - width: { - 0: '0', - '1/3': '33.33333%', - }, - minWidth: { - '1/3': '33.33333%', - }, - }, - }) +test('the theme function can resolve deep function values', () => { + const userConfig = { + theme: { + minWidth: (theme) => ({ + '1/3': theme('width.1/3'), + }), + }, + } + + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + content: [], + theme: { + spacing: { + 0: '0', + }, + width: (theme) => ({ + ...theme('spacing'), + '1/3': '33.33333%', + }), + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '-', + important: false, + separator: ':', + theme: { + spacing: { + 0: '0', + }, + width: { + 0: '0', + '1/3': '33.33333%', + }, + minWidth: { + '1/3': '33.33333%', + }, + }, }) +}) - test('theme values in the extend section are lazily evaluated', () => { - const userConfig = { - theme: { - colors: { - red: 'red', - green: 'green', - blue: 'blue', - }, - extend: { - colors: { - orange: 'orange', - }, - borderColor: (theme) => ({ - foo: theme('colors.orange'), - bar: theme('colors.red'), - }), - }, +test('theme values in the extend section are lazily evaluated', () => { + const userConfig = { + theme: { + colors: { + red: 'red', + green: 'green', + blue: 'blue', }, - } - - const defaultConfig = { - prefix: '-', - important: false, - separator: ':', - content: [], - theme: { + extend: { colors: { - cyan: 'cyan', - magenta: 'magenta', - yellow: 'yellow', + orange: 'orange', }, borderColor: (theme) => ({ - default: theme('colors.yellow', 'currentColor'), - ...theme('colors'), + foo: theme('colors.orange'), + bar: theme('colors.red'), }), }, - } - - const result = resolveConfig([userConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: '-', - important: false, - separator: ':', - theme: { - colors: { - orange: 'orange', - red: 'red', - green: 'green', - blue: 'blue', - }, - borderColor: { - default: 'currentColor', - foo: 'orange', - bar: 'red', - orange: 'orange', - red: 'red', - green: 'green', - blue: 'blue', - }, - }, - }) + }, + } + + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + content: [], + theme: { + colors: { + cyan: 'cyan', + magenta: 'magenta', + yellow: 'yellow', + }, + borderColor: (theme) => ({ + default: theme('colors.yellow', 'currentColor'), + ...theme('colors'), + }), + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '-', + important: false, + separator: ':', + theme: { + colors: { + orange: 'orange', + red: 'red', + green: 'green', + blue: 'blue', + }, + borderColor: { + default: 'currentColor', + foo: 'orange', + bar: 'red', + orange: 'orange', + red: 'red', + green: 'green', + blue: 'blue', + }, + }, }) +}) - test('lazily evaluated values have access to the config utils', () => { - const userConfig = { - theme: { - inset: (theme) => theme('margin'), - shift: (theme, { negative }) => ({ +test('lazily evaluated values have access to the config utils', () => { + const userConfig = { + theme: { + inset: (theme) => theme('margin'), + shift: (theme, { negative }) => ({ + ...theme('spacing'), + ...negative(theme('spacing')), + }), + extend: { + nudge: (theme, { negative }) => ({ ...theme('spacing'), ...negative(theme('spacing')), }), - extend: { - nudge: (theme, { negative }) => ({ - ...theme('spacing'), - ...negative(theme('spacing')), - }), - }, }, - } - - const defaultConfig = { - prefix: '-', - important: false, - separator: ':', - content: [], - theme: { - spacing: { - 1: '1px', - 2: '2px', - 3: '3px', - 4: '4px', - }, - margin: (theme, { negative }) => ({ - ...theme('spacing'), - ...negative(theme('spacing')), - }), - }, - } - - const result = resolveConfig([userConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: '-', - important: false, - separator: ':', - theme: { - spacing: { - 1: '1px', - 2: '2px', - 3: '3px', - 4: '4px', - }, - inset: { - '-1': '-1px', - '-2': '-2px', - '-3': '-3px', - '-4': '-4px', - 1: '1px', - 2: '2px', - 3: '3px', - 4: '4px', - }, - margin: { - '-1': '-1px', - '-2': '-2px', - '-3': '-3px', - '-4': '-4px', - 1: '1px', - 2: '2px', - 3: '3px', - 4: '4px', - }, - shift: { - '-1': '-1px', - '-2': '-2px', - '-3': '-3px', - '-4': '-4px', - 1: '1px', - 2: '2px', - 3: '3px', - 4: '4px', - }, - nudge: { - '-1': '-1px', - '-2': '-2px', - '-3': '-3px', - '-4': '-4px', - 1: '1px', - 2: '2px', - 3: '3px', - 4: '4px', - }, - }, - }) + }, + } + + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + content: [], + theme: { + spacing: { + 1: '1px', + 2: '2px', + 3: '3px', + 4: '4px', + }, + margin: (theme, { negative }) => ({ + ...theme('spacing'), + ...negative(theme('spacing')), + }), + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '-', + important: false, + separator: ':', + theme: { + spacing: { + 1: '1px', + 2: '2px', + 3: '3px', + 4: '4px', + }, + inset: { + '-1': '-1px', + '-2': '-2px', + '-3': '-3px', + '-4': '-4px', + 1: '1px', + 2: '2px', + 3: '3px', + 4: '4px', + }, + margin: { + '-1': '-1px', + '-2': '-2px', + '-3': '-3px', + '-4': '-4px', + 1: '1px', + 2: '2px', + 3: '3px', + 4: '4px', + }, + shift: { + '-1': '-1px', + '-2': '-2px', + '-3': '-3px', + '-4': '-4px', + 1: '1px', + 2: '2px', + 3: '3px', + 4: '4px', + }, + nudge: { + '-1': '-1px', + '-2': '-2px', + '-3': '-3px', + '-4': '-4px', + 1: '1px', + 2: '2px', + 3: '3px', + 4: '4px', + }, + }, }) +}) - test('the original theme is not mutated', () => { - const userConfig = { - theme: { - extend: { - colors: { - orange: 'orange', - }, +test('the original theme is not mutated', () => { + const userConfig = { + theme: { + extend: { + colors: { + orange: 'orange', }, }, - } + }, + } - const defaultConfig = { - prefix: '-', - important: false, - separator: ':', - content: [], - theme: { - colors: { - cyan: 'cyan', - magenta: 'magenta', - yellow: 'yellow', - }, + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + content: [], + theme: { + colors: { + cyan: 'cyan', + magenta: 'magenta', + yellow: 'yellow', }, - } + }, + } - resolveConfig([userConfig, defaultConfig]) + resolveConfig([userConfig, defaultConfig]) - expect(userConfig).toEqual({ - theme: { - extend: { - colors: { - orange: 'orange', - }, + expect(userConfig).toEqual({ + theme: { + extend: { + colors: { + orange: 'orange', }, }, - }) + }, }) +}) - test('custom properties are multiplied by -1 for negative values', () => { - const userConfig = { - theme: { - spacing: { - 0: 0, - 1: '1px', - 2: '2px', - 3: '3px', - 4: '4px', - auto: 'auto', - foo: 'var(--foo)', - bar: 'var(--bar, 500px)', - baz: 'calc(50% - 10px)', - qux: '10poops', - }, - margin: (theme, { negative }) => ({ - ...theme('spacing'), - ...negative(theme('spacing')), - }), - }, - } - - const defaultConfig = { - prefix: '-', - important: false, - separator: ':', - content: [], - theme: {}, - } - - const result = resolveConfig([userConfig, defaultConfig]) - - expect(result.theme.spacing).toEqual({ - 0: 0, - 1: '1px', - 2: '2px', - 3: '3px', - 4: '4px', - auto: 'auto', - foo: 'var(--foo)', - bar: 'var(--bar, 500px)', - baz: 'calc(50% - 10px)', - qux: '10poops', - }) - expect(result.theme.margin).toEqual({ - 0: 0, - 1: '1px', - 2: '2px', - 3: '3px', - 4: '4px', - auto: 'auto', - foo: 'var(--foo)', - bar: 'var(--bar, 500px)', - baz: 'calc(50% - 10px)', - qux: '10poops', - '-0': '0', - '-1': '-1px', - '-2': '-2px', - '-3': '-3px', - '-4': '-4px', - '-foo': 'calc(var(--foo) * -1)', - '-bar': 'calc(var(--bar, 500px) * -1)', - '-baz': 'calc(calc(50% - 10px) * -1)', - '-qux': '-10poops', - }) +test('custom properties are multiplied by -1 for negative values', () => { + const userConfig = { + theme: { + spacing: { + 0: 0, + 1: '1px', + 2: '2px', + 3: '3px', + 4: '4px', + auto: 'auto', + foo: 'var(--foo)', + bar: 'var(--bar, 500px)', + baz: 'calc(50% - 10px)', + qux: '10poops', + }, + margin: (theme, { negative }) => ({ + ...theme('spacing'), + ...negative(theme('spacing')), + }), + }, + } + + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + content: [], + theme: {}, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result.theme.spacing).toEqual({ + 0: 0, + 1: '1px', + 2: '2px', + 3: '3px', + 4: '4px', + auto: 'auto', + foo: 'var(--foo)', + bar: 'var(--bar, 500px)', + baz: 'calc(50% - 10px)', + qux: '10poops', + }) + expect(result.theme.margin).toEqual({ + 0: 0, + 1: '1px', + 2: '2px', + 3: '3px', + 4: '4px', + auto: 'auto', + foo: 'var(--foo)', + bar: 'var(--bar, 500px)', + baz: 'calc(50% - 10px)', + qux: '10poops', + '-0': '0', + '-1': '-1px', + '-2': '-2px', + '-3': '-3px', + '-4': '-4px', + '-foo': 'calc(var(--foo) * -1)', + '-bar': 'calc(var(--bar, 500px) * -1)', + '-baz': 'calc(calc(50% - 10px) * -1)', + '-qux': '-10poops', }) +}) - test('more than two config objects can be resolved', () => { - const firstConfig = { - theme: { - extend: { - fontFamily: () => ({ - code: ['Menlo', 'monospace'], - }), - colors: { - red: 'red', - }, - backgroundColor: { - customBackgroundOne: '#bada55', - }, - textDecorationColor: { - orange: 'orange', - }, +test('more than two config objects can be resolved', () => { + const firstConfig = { + theme: { + extend: { + fontFamily: () => ({ + code: ['Menlo', 'monospace'], + }), + colors: { + red: 'red', }, - }, - } - - const secondConfig = { - prefix: '-', - important: false, - separator: ':', - theme: { - extend: { - fontFamily: { - quote: ['Helvetica', 'serif'], - }, - colors: { - green: 'green', - }, - backgroundColor: { - customBackgroundTwo: '#facade', - }, - textDecorationColor: (theme) => theme('colors'), + backgroundColor: { + customBackgroundOne: '#bada55', }, - }, - } - - const thirdConfig = { - prefix: '-', - important: false, - separator: ':', - theme: { - extend: { - fontFamily: { - hero: ['Futura', 'sans-serif'], - }, - colors: { - pink: 'pink', - }, - backgroundColor: () => ({ - customBackgroundThree: '#c0ffee', - }), - textDecorationColor: { - lime: 'lime', - }, + textDecorationColor: { + orange: 'orange', }, }, - } + }, + } - const defaultConfig = { - prefix: '-', - important: false, - separator: ':', - content: [], - theme: { + const secondConfig = { + prefix: '-', + important: false, + separator: ':', + theme: { + extend: { fontFamily: { - body: ['Arial', 'sans-serif'], - display: ['Georgia', 'serif'], + quote: ['Helvetica', 'serif'], }, colors: { - blue: 'blue', + green: 'green', }, - backgroundColor: (theme) => theme('colors'), + backgroundColor: { + customBackgroundTwo: '#facade', + }, + textDecorationColor: (theme) => theme('colors'), }, - } + }, + } - const result = resolveConfig([firstConfig, secondConfig, thirdConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: '-', - important: false, - separator: ':', - theme: { + const thirdConfig = { + prefix: '-', + important: false, + separator: ':', + theme: { + extend: { fontFamily: { - body: ['Arial', 'sans-serif'], - display: ['Georgia', 'serif'], - code: ['Menlo', 'monospace'], - quote: ['Helvetica', 'serif'], hero: ['Futura', 'sans-serif'], }, colors: { - red: 'red', - green: 'green', - blue: 'blue', pink: 'pink', }, - backgroundColor: { - red: 'red', - green: 'green', - blue: 'blue', - pink: 'pink', - customBackgroundOne: '#bada55', - customBackgroundTwo: '#facade', + backgroundColor: () => ({ customBackgroundThree: '#c0ffee', - }, + }), textDecorationColor: { - red: 'red', - green: 'green', - blue: 'blue', - pink: 'pink', - orange: 'orange', lime: 'lime', }, }, - }) + }, + } + + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + content: [], + theme: { + fontFamily: { + body: ['Arial', 'sans-serif'], + display: ['Georgia', 'serif'], + }, + colors: { + blue: 'blue', + }, + backgroundColor: (theme) => theme('colors'), + }, + } + + const result = resolveConfig([firstConfig, secondConfig, thirdConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '-', + important: false, + separator: ':', + theme: { + fontFamily: { + body: ['Arial', 'sans-serif'], + display: ['Georgia', 'serif'], + code: ['Menlo', 'monospace'], + quote: ['Helvetica', 'serif'], + hero: ['Futura', 'sans-serif'], + }, + colors: { + red: 'red', + green: 'green', + blue: 'blue', + pink: 'pink', + }, + backgroundColor: { + red: 'red', + green: 'green', + blue: 'blue', + pink: 'pink', + customBackgroundOne: '#bada55', + customBackgroundTwo: '#facade', + customBackgroundThree: '#c0ffee', + }, + textDecorationColor: { + red: 'red', + green: 'green', + blue: 'blue', + pink: 'pink', + orange: 'orange', + lime: 'lime', + }, + }, }) +}) - test('plugin config modifications are applied', () => { - const userConfig = { - plugins: [ - { - config: { - prefix: 'tw-', - }, - }, - ], - } - - const defaultConfig = { - prefix: '', - important: false, - separator: ':', - content: [], - theme: { - screens: { - mobile: '400px', - }, - }, - } - - const result = resolveConfig([userConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: 'tw-', - important: false, - separator: ':', - theme: { - screens: { - mobile: '400px', - }, - }, - plugins: userConfig.plugins, - }) +test('plugin config modifications are applied', () => { + const userConfig = { + plugins: [ + { + config: { + prefix: 'tw-', + }, + }, + ], + } + + const defaultConfig = { + prefix: '', + important: false, + separator: ':', + content: [], + theme: { + screens: { + mobile: '400px', + }, + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: 'tw-', + important: false, + separator: ':', + theme: { + screens: { + mobile: '400px', + }, + }, + plugins: userConfig.plugins, }) +}) - test('user config takes precedence over plugin config modifications', () => { - const userConfig = { - prefix: 'user-', - plugins: [ - { - config: { - prefix: 'tw-', - }, - }, - ], - } - - const defaultConfig = { - prefix: '', - important: false, - separator: ':', - content: [], - theme: { - screens: { - mobile: '400px', - }, - }, - } - - const result = resolveConfig([userConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: 'user-', - important: false, - separator: ':', - theme: { - screens: { - mobile: '400px', - }, - }, - plugins: userConfig.plugins, - }) +test('user config takes precedence over plugin config modifications', () => { + const userConfig = { + prefix: 'user-', + plugins: [ + { + config: { + prefix: 'tw-', + }, + }, + ], + } + + const defaultConfig = { + prefix: '', + important: false, + separator: ':', + content: [], + theme: { + screens: { + mobile: '400px', + }, + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: 'user-', + important: false, + separator: ':', + theme: { + screens: { + mobile: '400px', + }, + }, + plugins: userConfig.plugins, }) +}) - test('plugin config can register plugins that also have config', () => { - const userConfig = { - plugins: [ - { - config: { - prefix: 'tw-', - plugins: [ - { - config: { - important: true, - }, +test('plugin config can register plugins that also have config', () => { + const userConfig = { + plugins: [ + { + config: { + prefix: 'tw-', + plugins: [ + { + config: { + important: true, }, - { - config: { - separator: '__', - }, + }, + { + config: { + separator: '__', }, - ], - }, - handler() {}, - }, - ], - } - - const defaultConfig = { - prefix: '', - important: false, - separator: ':', - content: [], - theme: { - screens: { - mobile: '400px', - }, - }, - } - - const result = resolveConfig([userConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: 'tw-', - important: true, - separator: '__', - theme: { - screens: { - mobile: '400px', + }, + ], }, + handler() {}, }, - plugins: userConfig.plugins, - }) - }) + ], + } - test('plugin configs take precedence over plugin configs registered by that plugin', () => { - const userConfig = { - plugins: [ - { - config: { - prefix: 'outer-', - plugins: [ - { - config: { - prefix: 'inner-', - }, - }, - ], - }, - handler() {}, - }, - ], - } - - const defaultConfig = { - prefix: '', - important: false, - separator: ':', - content: [], - theme: { - screens: { - mobile: '400px', - }, + const defaultConfig = { + prefix: '', + important: false, + separator: ':', + content: [], + theme: { + screens: { + mobile: '400px', }, - } + }, + } - const result = resolveConfig([userConfig, defaultConfig]) + const result = resolveConfig([userConfig, defaultConfig]) - expect(result).toMatchObject({ - prefix: 'outer-', - important: false, - separator: ':', - theme: { - screens: { - mobile: '400px', - }, + expect(result).toMatchObject({ + prefix: 'tw-', + important: true, + separator: '__', + theme: { + screens: { + mobile: '400px', }, - plugins: userConfig.plugins, - }) + }, + plugins: userConfig.plugins, }) +}) - test('plugin theme extensions are added even if user overrides top-level theme config', () => { - const userConfig = { - theme: { - width: { - '1px': '1px', - }, - }, - plugins: [ - { - config: { - theme: { - extend: { - width: { - '2px': '2px', - '3px': '3px', - }, +test('plugin configs take precedence over plugin configs registered by that plugin', () => { + const userConfig = { + plugins: [ + { + config: { + prefix: 'outer-', + plugins: [ + { + config: { + prefix: 'inner-', }, }, - }, - handler() {}, - }, - ], - } - - const defaultConfig = { - prefix: '', - important: false, - separator: ':', - content: [], - theme: { - width: { - sm: '1rem', - md: '2rem', - lg: '3rem', - }, - screens: { - mobile: '400px', + ], }, + handler() {}, }, - } + ], + } - const result = resolveConfig([userConfig, defaultConfig]) + const defaultConfig = { + prefix: '', + important: false, + separator: ':', + content: [], + theme: { + screens: { + mobile: '400px', + }, + }, + } - expect(result).toMatchObject({ - prefix: '', - important: false, - separator: ':', - theme: { - width: { - '1px': '1px', - '2px': '2px', - '3px': '3px', - }, - screens: { - mobile: '400px', - }, + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: 'outer-', + important: false, + separator: ':', + theme: { + screens: { + mobile: '400px', }, - plugins: userConfig.plugins, - }) + }, + plugins: userConfig.plugins, }) +}) - test('user theme extensions take precedence over plugin theme extensions with the same key', () => { - const userConfig = { - theme: { - extend: { - width: { - xl: '6rem', - }, - }, +test('plugin theme extensions are added even if user overrides top-level theme config', () => { + const userConfig = { + theme: { + width: { + '1px': '1px', }, - plugins: [ - { - config: { - theme: { - extend: { - width: { - xl: '4rem', - }, + }, + plugins: [ + { + config: { + theme: { + extend: { + width: { + '2px': '2px', + '3px': '3px', }, }, }, - handler() {}, - }, - ], - } - - const defaultConfig = { - prefix: '', - important: false, - separator: ':', - content: [], - theme: { - width: { - sm: '1rem', - md: '2rem', - lg: '3rem', }, - screens: { - mobile: '400px', - }, - }, - } - - const result = resolveConfig([userConfig, defaultConfig]) + handler() {}, + }, + ], + } + + const defaultConfig = { + prefix: '', + important: false, + separator: ':', + content: [], + theme: { + width: { + sm: '1rem', + md: '2rem', + lg: '3rem', + }, + screens: { + mobile: '400px', + }, + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '', + important: false, + separator: ':', + theme: { + width: { + '1px': '1px', + '2px': '2px', + '3px': '3px', + }, + screens: { + mobile: '400px', + }, + }, + plugins: userConfig.plugins, + }) +}) - expect(result).toMatchObject({ - prefix: '', - important: false, - separator: ':', - theme: { +test('user theme extensions take precedence over plugin theme extensions with the same key', () => { + const userConfig = { + theme: { + extend: { width: { - sm: '1rem', - md: '2rem', - lg: '3rem', xl: '6rem', }, - screens: { - mobile: '400px', - }, }, - plugins: userConfig.plugins, - }) - }) - - test('extensions are applied in the right order', () => { - const userConfig = { - theme: { - extend: { - colors: { - grey: { - light: '#eee', + }, + plugins: [ + { + config: { + theme: { + extend: { + width: { + xl: '4rem', + }, }, }, }, - }, - } + handler() {}, + }, + ], + } + + const defaultConfig = { + prefix: '', + important: false, + separator: ':', + content: [], + theme: { + width: { + sm: '1rem', + md: '2rem', + lg: '3rem', + }, + screens: { + mobile: '400px', + }, + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '', + important: false, + separator: ':', + theme: { + width: { + sm: '1rem', + md: '2rem', + lg: '3rem', + xl: '6rem', + }, + screens: { + mobile: '400px', + }, + }, + plugins: userConfig.plugins, + }) +}) - const otherConfig = { - theme: { - extend: { - colors: { - grey: { - light: '#ddd', - darker: '#111', - }, +test('extensions are applied in the right order', () => { + const userConfig = { + theme: { + extend: { + colors: { + grey: { + light: '#eee', }, }, }, - } + }, + } - const anotherConfig = { - theme: { - extend: { - colors: { - grey: { - darker: '#222', - }, + const otherConfig = { + theme: { + extend: { + colors: { + grey: { + light: '#ddd', + darker: '#111', }, }, }, - } + }, + } - const defaultConfig = { - content: [], - theme: { + const anotherConfig = { + theme: { + extend: { colors: { grey: { - light: '#ccc', - dark: '#333', + darker: '#222', }, }, }, - } + }, + } - const result = resolveConfig([userConfig, otherConfig, anotherConfig, defaultConfig]) + const defaultConfig = { + content: [], + theme: { + colors: { + grey: { + light: '#ccc', + dark: '#333', + }, + }, + }, + } - expect(result).toMatchObject({ - theme: { - colors: { - grey: { - light: '#eee', - dark: '#333', - darker: '#111', - }, + const result = resolveConfig([userConfig, otherConfig, anotherConfig, defaultConfig]) + + expect(result).toMatchObject({ + theme: { + colors: { + grey: { + light: '#eee', + dark: '#333', + darker: '#111', }, }, - }) + }, }) +}) - test('core plugin configuration builds on the default list when starting with an empty object', () => { - const userConfig = { - corePlugins: { display: false }, - } - - const defaultConfig = { - prefix: '', - important: false, - separator: ':', - content: [], - theme: {}, - corePlugins: {}, - } - - const result = resolveConfig([userConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: '', - important: false, - separator: ':', - theme: {}, - corePlugins: corePluginList.filter((c) => c !== 'display'), - }) +test('core plugin configuration builds on the default list when starting with an empty object', () => { + const userConfig = { + corePlugins: { display: false }, + } + + const defaultConfig = { + prefix: '', + important: false, + separator: ':', + content: [], + theme: {}, + corePlugins: {}, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '', + important: false, + separator: ':', + theme: {}, + corePlugins: corePluginList.filter((c) => c !== 'display'), }) +}) - test('core plugins that are disabled by default can be enabled', () => { - const userConfig = { - corePlugins: { display: true }, - } - - const defaultConfig = { - presets: [], - prefix: '', - important: false, - separator: ':', - content: [], - theme: {}, - corePlugins: { display: false }, - } - - const result = resolveConfig([userConfig, defaultConfig]) - expect(result.corePlugins).toContain('display') - }) +test('core plugins that are disabled by default can be enabled', () => { + const userConfig = { + corePlugins: { display: true }, + } + + const defaultConfig = { + presets: [], + prefix: '', + important: false, + separator: ':', + content: [], + theme: {}, + corePlugins: { display: false }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + expect(result.corePlugins).toContain('display') +}) - test('core plugin configurations stack', () => { - const userConfig = { - corePlugins: { display: false }, - } - - const otherConfig = { - corePlugins: ({ corePlugins }) => { - return [...corePlugins, 'margin'] - }, - } - - const defaultConfig = { - prefix: '', - important: false, - separator: ':', - content: [], - theme: {}, - corePlugins: ['float', 'display', 'padding'], - } - - const result = resolveConfig([userConfig, otherConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: '', - important: false, - separator: ':', - theme: {}, - corePlugins: ['float', 'padding', 'margin'], - }) +test('core plugin configurations stack', () => { + const userConfig = { + corePlugins: { display: false }, + } + + const otherConfig = { + corePlugins: ({ corePlugins }) => { + return [...corePlugins, 'margin'] + }, + } + + const defaultConfig = { + prefix: '', + important: false, + separator: ':', + content: [], + theme: {}, + corePlugins: ['float', 'display', 'padding'], + } + + const result = resolveConfig([userConfig, otherConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '', + important: false, + separator: ':', + theme: {}, + corePlugins: ['float', 'padding', 'margin'], }) +}) - test('plugins are merged', () => { - const userConfig = { - plugins: ['3'], - } - - const otherConfig = { - plugins: ['2'], - } - - const defaultConfig = { - plugins: ['1'], - prefix: '', - important: false, - separator: ':', - content: [], - theme: {}, - } - - const result = resolveConfig([userConfig, otherConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: '', - important: false, - separator: ':', - theme: {}, - plugins: ['1', '2', '3'], - }) +test('plugins are merged', () => { + const userConfig = { + plugins: ['3'], + } + + const otherConfig = { + plugins: ['2'], + } + + const defaultConfig = { + plugins: ['1'], + prefix: '', + important: false, + separator: ':', + content: [], + theme: {}, + } + + const result = resolveConfig([userConfig, otherConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '', + important: false, + separator: ':', + theme: {}, + plugins: ['1', '2', '3'], }) +}) - test('all helpers can be destructured from the first function argument', () => { - const userConfig = { - theme: { - example: ({ theme, colors, negative, breakpoints }) => ({ - weight: theme('fontWeight.bold'), - black: colors.black, - white: colors.white, - ...negative(theme('spacing')), - ...breakpoints(theme('screens')), - }), - }, - } - - const defaultConfig = { - prefix: '-', - important: false, - separator: ':', - content: [], - theme: { - screens: { - sm: '640px', - md: '768px', - }, - fontWeight: { - bold: 700, - }, - spacing: { - 0: '0px', - 1: '1px', - 2: '2px', - 3: '3px', - 4: '4px', - }, - }, - } - - const result = resolveConfig([userConfig, defaultConfig]) - - expect(result).toMatchObject({ - prefix: '-', - important: false, - separator: ':', - theme: { - example: { - weight: 700, - black: '#000', - white: '#fff', - '-1': '-1px', - '-2': '-2px', - '-3': '-3px', - '-4': '-4px', - 'screen-sm': '640px', - 'screen-md': '768px', - }, - }, - }) +test('all helpers can be destructured from the first function argument', () => { + const userConfig = { + theme: { + example: ({ theme, colors, negative, breakpoints }) => ({ + weight: theme('fontWeight.bold'), + black: colors.black, + white: colors.white, + ...negative(theme('spacing')), + ...breakpoints(theme('screens')), + }), + }, + } + + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + content: [], + theme: { + screens: { + sm: '640px', + md: '768px', + }, + fontWeight: { + bold: 700, + }, + spacing: { + 0: '0px', + 1: '1px', + 2: '2px', + 3: '3px', + 4: '4px', + }, + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toMatchObject({ + prefix: '-', + important: false, + separator: ':', + theme: { + example: { + weight: 700, + black: '#000', + white: '#fff', + '-1': '-1px', + '-2': '-2px', + '-3': '-3px', + '-4': '-4px', + 'screen-sm': '640px', + 'screen-md': '768px', + }, + }, }) +}) - test('does not duplicate extended configs every time resolveConfig is called', () => { - let shared = { - foo: { bar: { baz: [{ color: 'red' }] } }, - } - - const createConfig = (color) => - resolveConfig([ - { - theme: { - foo: shared.foo, - extend: { - foo: { bar: { baz: { color } } }, - }, +test('does not duplicate extended configs every time resolveConfig is called', () => { + let shared = { + foo: { bar: { baz: [{ color: 'red' }] } }, + } + + const createConfig = (color) => + resolveConfig([ + { + theme: { + foo: shared.foo, + extend: { + foo: { bar: { baz: { color } } }, }, }, - ]) + }, + ]) - createConfig('orange') - createConfig('yellow') - createConfig('green') + createConfig('orange') + createConfig('yellow') + createConfig('green') - const result = createConfig('blue') + const result = createConfig('blue') - expect(shared.foo.bar.baz).toMatchObject([{ color: 'red' }]) - expect(result.theme.foo.bar.baz).toMatchObject([{ color: 'red' }, { color: 'blue' }]) - }) + expect(shared.foo.bar.baz).toMatchObject([{ color: 'red' }]) + expect(result.theme.foo.bar.baz).toMatchObject([{ color: 'red' }, { color: 'blue' }]) }) diff --git a/tests/responsive-and-variants-atrules.test.js b/tests/responsive-and-variants-atrules.test.js index 9d9ed5d85cea..4d4a0f507b9e 100644 --- a/tests/responsive-and-variants-atrules.test.js +++ b/tests/responsive-and-variants-atrules.test.js @@ -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` -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - } +test('responsive and variants atrules', () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + } - let input = css` - @tailwind components; - @tailwind utilities; + let input = css` + @tailwind components; + @tailwind utilities; - @layer utilities { - @responsive { - .responsive-in-utilities { - color: blue; - } + @layer utilities { + @responsive { + .responsive-in-utilities { + color: blue; + } + } + @variants { + .variants-in-utilities { + color: red; } + } + @responsive { @variants { - .variants-in-utilities { - color: red; + .both-in-utilities { + color: green; } } - @responsive { - @variants { - .both-in-utilities { - color: green; - } - } + } + } + + @responsive { + .responsive-at-root { + color: white; + } + } + @variants { + .variants-at-root { + color: orange; + } + } + @responsive { + @variants { + .both-at-root { + color: pink; } } + } + @layer components { @responsive { - .responsive-at-root { - color: white; + .responsive-in-components { + color: blue; } } @variants { - .variants-at-root { - color: orange; + .variants-in-components { + color: red; } } @responsive { @variants { - .both-at-root { - color: pink; + .both-in-components { + color: green; } } } + } + ` - @layer components { - @responsive { - .responsive-in-components { - color: blue; - } - } - @variants { - .variants-in-components { - color: red; - } - } - @responsive { - @variants { - .both-in-components { - color: green; - } - } - } + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .responsive-in-components { + color: #00f; } - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .responsive-in-components { + .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; } - .variants-in-components { + .md\:focus\:variants-in-components:focus { color: red; } - .both-in-components { + .md\:focus\:both-in-components:focus { color: green; } - .responsive-in-utilities { + .md\:focus\:responsive-in-utilities:focus { color: #00f; } - .variants-in-utilities { + .md\:focus\:variants-in-utilities:focus { color: red; } - .both-in-utilities { + .md\:focus\:both-in-utilities:focus { color: green; } - .responsive-at-root { + .md\:focus\:responsive-at-root:focus { color: #fff; } - .variants-at-root { + .md\:focus\:variants-at-root:focus { color: orange; } - .both-at-root { + .md\:focus\:both-at-root:focus { 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; - } - } - `) - }) + } + `) }) }) diff --git a/tests/safelist.test.js b/tests/safelist.test.js index cbbd1e245821..e2621f846e7c 100644 --- a/tests/safelist.test.js +++ b/tests/safelist.test.js @@ -1,636 +1,491 @@ -import { crosscheck, run, html, css } from './util/run' +import { run, html, css } from './util/run' -crosscheck(({ stable, oxide }) => { - it('should not safelist anything', () => { - let config = { - content: [{ raw: html`
` }], - } +it('should not safelist anything', () => { + let config = { + content: [{ raw: html`
` }], + } - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .uppercase { - text-transform: uppercase; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .uppercase { + text-transform: uppercase; + } + `) }) +}) - it('should safelist strings', () => { - let config = { - content: [{ raw: html`
` }], - safelist: ['mt-[20px]', 'font-bold', 'text-gray-200', 'hover:underline'], - } +it('should safelist strings', () => { + let config = { + content: [{ raw: html`
` }], + safelist: ['mt-[20px]', 'font-bold', 'text-gray-200', 'hover:underline'], + } - return run('@tailwind utilities', config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .mt-\[20px\] { - margin-top: 20px; - } - .font-bold { - font-weight: 700; - } - .uppercase { - text-transform: uppercase; - } - .text-gray-200 { - --tw-text-opacity: 1; - color: rgb(229 231 235 / var(--tw-text-opacity)); - } - .hover\:underline:hover { - text-decoration-line: underline; - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .mt-\[20px\] { - margin-top: 20px; - } - .font-bold { - font-weight: 700; - } - .uppercase { - text-transform: uppercase; - } - .text-gray-200 { - color: #e5e7eb; - } - .hover\:underline:hover { - text-decoration-line: underline; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .mt-\[20px\] { + margin-top: 20px; + } + .font-bold { + font-weight: 700; + } + .uppercase { + text-transform: uppercase; + } + .text-gray-200 { + --tw-text-opacity: 1; + color: rgb(229 231 235 / var(--tw-text-opacity)); + } + .hover\:underline:hover { + text-decoration-line: underline; + } + `) }) +}) - it('should safelist based on a pattern regex', () => { - let config = { - content: [{ raw: html`
` }], - safelist: [ - { - pattern: /^bg-(red)-(100|200)$/, - variants: ['hover'], - }, - ], - } +it('should safelist based on a pattern regex', () => { + let config = { + content: [{ raw: html`
` }], + safelist: [ + { + pattern: /^bg-(red)-(100|200)$/, + variants: ['hover'], + }, + ], + } - return run('@tailwind utilities', config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .bg-red-100 { - --tw-bg-opacity: 1; - background-color: rgb(254 226 226 / var(--tw-bg-opacity)); - } - .bg-red-200 { - --tw-bg-opacity: 1; - background-color: rgb(254 202 202 / var(--tw-bg-opacity)); - } - .uppercase { - text-transform: uppercase; - } - .hover\:bg-red-100:hover { - --tw-bg-opacity: 1; - background-color: rgb(254 226 226 / var(--tw-bg-opacity)); - } - .hover\:bg-red-200:hover { - --tw-bg-opacity: 1; - background-color: rgb(254 202 202 / var(--tw-bg-opacity)); - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .bg-red-100 { - background-color: #fee2e2; - } - .bg-red-200 { - background-color: #fecaca; - } - .uppercase { - text-transform: uppercase; - } - .hover\:bg-red-100:hover { - background-color: #fee2e2; - } - .hover\:bg-red-200:hover { - background-color: #fecaca; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .bg-red-100 { + --tw-bg-opacity: 1; + background-color: rgb(254 226 226 / var(--tw-bg-opacity)); + } + .bg-red-200 { + --tw-bg-opacity: 1; + background-color: rgb(254 202 202 / var(--tw-bg-opacity)); + } + .uppercase { + text-transform: uppercase; + } + .hover\:bg-red-100:hover { + --tw-bg-opacity: 1; + background-color: rgb(254 226 226 / var(--tw-bg-opacity)); + } + .hover\:bg-red-200:hover { + --tw-bg-opacity: 1; + background-color: rgb(254 202 202 / var(--tw-bg-opacity)); + } + `) }) +}) - it('should not generate duplicates', () => { - let config = { - content: [{ raw: html`
` }], - safelist: [ - 'uppercase', - { - pattern: /^bg-(red)-(100|200)$/, - variants: ['hover'], - }, - { - pattern: /^bg-(red)-(100|200)$/, - variants: ['hover'], - }, - { - pattern: /^bg-(red)-(100|200)$/, - variants: ['hover'], - }, - ], - } +it('should not generate duplicates', () => { + let config = { + content: [{ raw: html`
` }], + safelist: [ + 'uppercase', + { + pattern: /^bg-(red)-(100|200)$/, + variants: ['hover'], + }, + { + pattern: /^bg-(red)-(100|200)$/, + variants: ['hover'], + }, + { + pattern: /^bg-(red)-(100|200)$/, + variants: ['hover'], + }, + ], + } - return run('@tailwind utilities', config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .bg-red-100 { - --tw-bg-opacity: 1; - background-color: rgb(254 226 226 / var(--tw-bg-opacity)); - } - .bg-red-200 { - --tw-bg-opacity: 1; - background-color: rgb(254 202 202 / var(--tw-bg-opacity)); - } - .uppercase { - text-transform: uppercase; - } - .hover\:bg-red-100:hover { - --tw-bg-opacity: 1; - background-color: rgb(254 226 226 / var(--tw-bg-opacity)); - } - .hover\:bg-red-200:hover { - --tw-bg-opacity: 1; - background-color: rgb(254 202 202 / var(--tw-bg-opacity)); - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .bg-red-100 { - background-color: #fee2e2; - } - .bg-red-200 { - background-color: #fecaca; - } - .uppercase { - text-transform: uppercase; - } - .hover\:bg-red-100:hover { - background-color: #fee2e2; - } - .hover\:bg-red-200:hover { - background-color: #fecaca; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .bg-red-100 { + --tw-bg-opacity: 1; + background-color: rgb(254 226 226 / var(--tw-bg-opacity)); + } + .bg-red-200 { + --tw-bg-opacity: 1; + background-color: rgb(254 202 202 / var(--tw-bg-opacity)); + } + .uppercase { + text-transform: uppercase; + } + .hover\:bg-red-100:hover { + --tw-bg-opacity: 1; + background-color: rgb(254 226 226 / var(--tw-bg-opacity)); + } + .hover\:bg-red-200:hover { + --tw-bg-opacity: 1; + background-color: rgb(254 202 202 / var(--tw-bg-opacity)); + } + `) }) +}) - it('should safelist when using a custom prefix', () => { - let config = { - prefix: 'tw-', - content: [{ raw: html`
` }], - safelist: [ - { - pattern: /^tw-bg-red-(100|200)$/g, - }, - ], - } +it('should safelist when using a custom prefix', () => { + let config = { + prefix: 'tw-', + content: [{ raw: html`
` }], + safelist: [ + { + pattern: /^tw-bg-red-(100|200)$/g, + }, + ], + } - return run('@tailwind utilities', config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .tw-bg-red-100 { - --tw-bg-opacity: 1; - background-color: rgb(254 226 226 / var(--tw-bg-opacity)); - } - .tw-bg-red-200 { - --tw-bg-opacity: 1; - background-color: rgb(254 202 202 / var(--tw-bg-opacity)); - } - .tw-uppercase { - text-transform: uppercase; - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .tw-bg-red-100 { - background-color: #fee2e2; - } - .tw-bg-red-200 { - background-color: #fecaca; - } - .tw-uppercase { - text-transform: uppercase; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .tw-bg-red-100 { + --tw-bg-opacity: 1; + background-color: rgb(254 226 226 / var(--tw-bg-opacity)); + } + .tw-bg-red-200 { + --tw-bg-opacity: 1; + background-color: rgb(254 202 202 / var(--tw-bg-opacity)); + } + .tw-uppercase { + text-transform: uppercase; + } + `) }) +}) - it('should not safelist when an empty list is provided', () => { - let config = { - content: [{ raw: html`
` }], - safelist: [], - } +it('should not safelist when an empty list is provided', () => { + let config = { + content: [{ raw: html`
` }], + safelist: [], + } - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .uppercase { - text-transform: uppercase; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .uppercase { + text-transform: uppercase; + } + `) }) +}) - it('should not safelist when an sparse/holey list is provided', () => { - let config = { - content: [{ raw: html`
` }], - safelist: [, , ,], - } +it('should not safelist when an sparse/holey list is provided', () => { + let config = { + content: [{ raw: html`
` }], + safelist: [, , ,], + } - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .uppercase { - text-transform: uppercase; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .uppercase { + text-transform: uppercase; + } + `) }) +}) - it('should not safelist any invalid variants if provided', () => { - let config = { - content: [{ raw: html`
` }], - safelist: [ - { - pattern: /^bg-(red)-(100|200)$/, - variants: ['foo', 'bar'], - }, - ], - } +it('should not safelist any invalid variants if provided', () => { + let config = { + content: [{ raw: html`
` }], + safelist: [ + { + pattern: /^bg-(red)-(100|200)$/, + variants: ['foo', 'bar'], + }, + ], + } - return run('@tailwind utilities', config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .bg-red-100 { - --tw-bg-opacity: 1; - background-color: rgb(254 226 226 / var(--tw-bg-opacity)); - } - .bg-red-200 { - --tw-bg-opacity: 1; - background-color: rgb(254 202 202 / var(--tw-bg-opacity)); - } - .uppercase { - text-transform: uppercase; - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .bg-red-100 { - background-color: #fee2e2; - } - .bg-red-200 { - background-color: #fecaca; - } - .uppercase { - text-transform: uppercase; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .bg-red-100 { + --tw-bg-opacity: 1; + background-color: rgb(254 226 226 / var(--tw-bg-opacity)); + } + .bg-red-200 { + --tw-bg-opacity: 1; + background-color: rgb(254 202 202 / var(--tw-bg-opacity)); + } + .uppercase { + text-transform: uppercase; + } + `) }) +}) - it('should safelist negatives based on a pattern regex', () => { - let config = { - content: [{ raw: html`
` }], - safelist: [ - { - pattern: /^-top-1$/, - variants: ['hover'], - }, - ], - } +it('should safelist negatives based on a pattern regex', () => { + let config = { + content: [{ raw: html`
` }], + safelist: [ + { + pattern: /^-top-1$/, + variants: ['hover'], + }, + ], + } - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .-top-1 { - top: -0.25rem; - } - .uppercase { - text-transform: uppercase; - } - .hover\:-top-1:hover { - top: -0.25rem; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .-top-1 { + top: -0.25rem; + } + .uppercase { + text-transform: uppercase; + } + .hover\:-top-1:hover { + top: -0.25rem; + } + `) }) +}) - it('should safelist negatives based on a pattern regex', () => { - let config = { - content: [{ raw: html`
` }], - safelist: [ - { - pattern: /^bg-red-(400|500)(\/(40|50))?$/, - variants: ['hover'], - }, - { - pattern: /^(fill|ring|text)-red-200\/50$/, - variants: ['hover'], - }, - ], - } +it('should safelist negatives based on a pattern regex', () => { + let config = { + content: [{ raw: html`
` }], + safelist: [ + { + pattern: /^bg-red-(400|500)(\/(40|50))?$/, + variants: ['hover'], + }, + { + pattern: /^(fill|ring|text)-red-200\/50$/, + variants: ['hover'], + }, + ], + } - 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)); - } - .bg-red-400\/40 { - background-color: #f8717166; - } - .bg-red-400\/50 { - background-color: #f8717180; - } - .bg-red-500 { - --tw-bg-opacity: 1; - background-color: rgb(239 68 68 / var(--tw-bg-opacity)); - } - .bg-red-500\/40 { - background-color: #ef444466; - } - .bg-red-500\/50 { - background-color: #ef444480; - } - .fill-red-200\/50 { - fill: #fecaca80; - } - .uppercase { - text-transform: uppercase; - } - .text-red-200\/50 { - color: #fecaca80; - } - .ring-red-200\/50 { - --tw-ring-color: #fecaca80; - } - .hover\:bg-red-400:hover { - --tw-bg-opacity: 1; - background-color: rgb(248 113 113 / var(--tw-bg-opacity)); - } - .hover\:bg-red-400\/40:hover { - background-color: #f8717166; - } - .hover\:bg-red-400\/50:hover { - background-color: #f8717180; - } - .hover\:bg-red-500:hover { - --tw-bg-opacity: 1; - background-color: rgb(239 68 68 / var(--tw-bg-opacity)); - } - .hover\:bg-red-500\/40:hover { - background-color: #ef444466; - } - .hover\:bg-red-500\/50:hover { - background-color: #ef444480; - } - .hover\:fill-red-200\/50:hover { - fill: #fecaca80; - } - .hover\:text-red-200\/50:hover { - color: #fecaca80; - } - .hover\:ring-red-200\/50:hover { - --tw-ring-color: #fecaca80; - } - `) - }) + 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)); + } + .bg-red-400\/40 { + background-color: #f8717166; + } + .bg-red-400\/50 { + background-color: #f8717180; + } + .bg-red-500 { + --tw-bg-opacity: 1; + background-color: rgb(239 68 68 / var(--tw-bg-opacity)); + } + .bg-red-500\/40 { + background-color: #ef444466; + } + .bg-red-500\/50 { + background-color: #ef444480; + } + .fill-red-200\/50 { + fill: #fecaca80; + } + .uppercase { + text-transform: uppercase; + } + .text-red-200\/50 { + color: #fecaca80; + } + .ring-red-200\/50 { + --tw-ring-color: #fecaca80; + } + .hover\:bg-red-400:hover { + --tw-bg-opacity: 1; + background-color: rgb(248 113 113 / var(--tw-bg-opacity)); + } + .hover\:bg-red-400\/40:hover { + background-color: #f8717166; + } + .hover\:bg-red-400\/50:hover { + background-color: #f8717180; + } + .hover\:bg-red-500:hover { + --tw-bg-opacity: 1; + background-color: rgb(239 68 68 / var(--tw-bg-opacity)); + } + .hover\:bg-red-500\/40:hover { + background-color: #ef444466; + } + .hover\:bg-red-500\/50:hover { + background-color: #ef444480; + } + .hover\:fill-red-200\/50:hover { + fill: #fecaca80; + } + .hover\:text-red-200\/50:hover { + color: #fecaca80; + } + .hover\:ring-red-200\/50:hover { + --tw-ring-color: #fecaca80; + } + `) }) +}) - it('should safelist pattern regex with !important selector', () => { - let config = { - content: [{ raw: html`
` }], - safelist: [{ pattern: /^!grid-cols-(4|5|6)$/ }], - } +it('should safelist pattern regex with !important selector', () => { + let config = { + content: [{ raw: html`
` }], + safelist: [{ pattern: /^!grid-cols-(4|5|6)$/ }], + } - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .\!grid-cols-4 { - grid-template-columns: repeat(4, minmax(0, 1fr)) !important; - } - .\!grid-cols-5 { - grid-template-columns: repeat(5, minmax(0, 1fr)) !important; - } - .\!grid-cols-6 { - grid-template-columns: repeat(6, minmax(0, 1fr)) !important; - } - .uppercase { - text-transform: uppercase; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .\!grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)) !important; + } + .\!grid-cols-5 { + grid-template-columns: repeat(5, minmax(0, 1fr)) !important; + } + .\!grid-cols-6 { + grid-template-columns: repeat(6, minmax(0, 1fr)) !important; + } + .uppercase { + text-transform: uppercase; + } + `) }) +}) - it('should safelist pattern regex with custom prefix along with !important selector', () => { - let config = { - prefix: 'tw-', - content: [{ raw: html`
` }], - safelist: [{ pattern: /^!tw-grid-cols-(4|5|6)$/ }], - } +it('should safelist pattern regex with custom prefix along with !important selector', () => { + let config = { + prefix: 'tw-', + content: [{ raw: html`
` }], + safelist: [{ pattern: /^!tw-grid-cols-(4|5|6)$/ }], + } - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .\!tw-grid-cols-4 { - grid-template-columns: repeat(4, minmax(0, 1fr)) !important; - } - .\!tw-grid-cols-5 { - grid-template-columns: repeat(5, minmax(0, 1fr)) !important; - } - .\!tw-grid-cols-6 { - grid-template-columns: repeat(6, minmax(0, 1fr)) !important; - } - .tw-uppercase { - text-transform: uppercase; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .\!tw-grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)) !important; + } + .\!tw-grid-cols-5 { + grid-template-columns: repeat(5, minmax(0, 1fr)) !important; + } + .\!tw-grid-cols-6 { + grid-template-columns: repeat(6, minmax(0, 1fr)) !important; + } + .tw-uppercase { + text-transform: uppercase; + } + `) }) +}) - it('should safelist pattern regex having !important selector with variants', () => { - let config = { - content: [{ raw: html`
` }], - safelist: [ - { - pattern: /^!bg-gray-(500|600|700|800)$/, - variants: ['hover'], - }, - ], - } +it('should safelist pattern regex having !important selector with variants', () => { + let config = { + content: [{ raw: html`
` }], + safelist: [ + { + pattern: /^!bg-gray-(500|600|700|800)$/, + variants: ['hover'], + }, + ], + } - return run('@tailwind utilities', config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .\!bg-gray-500 { - --tw-bg-opacity: 1 !important; - background-color: rgb(107 114 128 / var(--tw-bg-opacity)) !important; - } - .\!bg-gray-600 { - --tw-bg-opacity: 1 !important; - background-color: rgb(75 85 99 / var(--tw-bg-opacity)) !important; - } - .\!bg-gray-700 { - --tw-bg-opacity: 1 !important; - background-color: rgb(55 65 81 / var(--tw-bg-opacity)) !important; - } - .\!bg-gray-800 { - --tw-bg-opacity: 1 !important; - background-color: rgb(31 41 55 / var(--tw-bg-opacity)) !important; - } - .uppercase { - text-transform: uppercase; - } - .hover\:\!bg-gray-500:hover { - --tw-bg-opacity: 1 !important; - background-color: rgb(107 114 128 / var(--tw-bg-opacity)) !important; - } - .hover\:\!bg-gray-600:hover { - --tw-bg-opacity: 1 !important; - background-color: rgb(75 85 99 / var(--tw-bg-opacity)) !important; - } - .hover\:\!bg-gray-700:hover { - --tw-bg-opacity: 1 !important; - background-color: rgb(55 65 81 / var(--tw-bg-opacity)) !important; - } - .hover\:\!bg-gray-800:hover { - --tw-bg-opacity: 1 !important; - background-color: rgb(31 41 55 / var(--tw-bg-opacity)) !important; - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .\!bg-gray-500 { - background-color: #6b7280 !important; - } - .\!bg-gray-600 { - background-color: #4b5563 !important; - } - .\!bg-gray-700 { - background-color: #374151 !important; - } - .\!bg-gray-800 { - background-color: #1f2937 !important; - } - .uppercase { - text-transform: uppercase; - } - .hover\:\!bg-gray-500:hover { - background-color: #6b7280 !important; - } - .hover\:\!bg-gray-600:hover { - background-color: #4b5563 !important; - } - .hover\:\!bg-gray-700:hover { - background-color: #374151 !important; - } - .hover\:\!bg-gray-800:hover { - background-color: #1f2937 !important; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .\!bg-gray-500 { + --tw-bg-opacity: 1 !important; + background-color: rgb(107 114 128 / var(--tw-bg-opacity)) !important; + } + .\!bg-gray-600 { + --tw-bg-opacity: 1 !important; + background-color: rgb(75 85 99 / var(--tw-bg-opacity)) !important; + } + .\!bg-gray-700 { + --tw-bg-opacity: 1 !important; + background-color: rgb(55 65 81 / var(--tw-bg-opacity)) !important; + } + .\!bg-gray-800 { + --tw-bg-opacity: 1 !important; + background-color: rgb(31 41 55 / var(--tw-bg-opacity)) !important; + } + .uppercase { + text-transform: uppercase; + } + .hover\:\!bg-gray-500:hover { + --tw-bg-opacity: 1 !important; + background-color: rgb(107 114 128 / var(--tw-bg-opacity)) !important; + } + .hover\:\!bg-gray-600:hover { + --tw-bg-opacity: 1 !important; + background-color: rgb(75 85 99 / var(--tw-bg-opacity)) !important; + } + .hover\:\!bg-gray-700:hover { + --tw-bg-opacity: 1 !important; + background-color: rgb(55 65 81 / var(--tw-bg-opacity)) !important; + } + .hover\:\!bg-gray-800:hover { + --tw-bg-opacity: 1 !important; + background-color: rgb(31 41 55 / var(--tw-bg-opacity)) !important; + } + `) }) +}) - it('should safelist multiple patterns with !important selector', () => { - let config = { - content: [{ raw: html`
` }], - safelist: [ - { - pattern: /^!text-gray-(700|800|900)$/, - variants: ['hover'], - }, - { - pattern: /^!bg-gray-(200|300|400)$/, - variants: ['hover'], - }, - ], - } +it('should safelist multiple patterns with !important selector', () => { + let config = { + content: [{ raw: html`
` }], + safelist: [ + { + pattern: /^!text-gray-(700|800|900)$/, + variants: ['hover'], + }, + { + pattern: /^!bg-gray-(200|300|400)$/, + variants: ['hover'], + }, + ], + } - return run('@tailwind utilities', config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .\!bg-gray-200 { - --tw-bg-opacity: 1 !important; - background-color: rgb(229 231 235 / var(--tw-bg-opacity)) !important; - } - .\!bg-gray-300 { - --tw-bg-opacity: 1 !important; - background-color: rgb(209 213 219 / var(--tw-bg-opacity)) !important; - } - .\!bg-gray-400 { - --tw-bg-opacity: 1 !important; - background-color: rgb(156 163 175 / var(--tw-bg-opacity)) !important; - } - .uppercase { - text-transform: uppercase; - } - .\!text-gray-700 { - --tw-text-opacity: 1 !important; - color: rgb(55 65 81 / var(--tw-text-opacity)) !important; - } - .\!text-gray-800 { - --tw-text-opacity: 1 !important; - color: rgb(31 41 55 / var(--tw-text-opacity)) !important; - } - .\!text-gray-900 { - --tw-text-opacity: 1 !important; - color: rgb(17 24 39 / var(--tw-text-opacity)) !important; - } - .hover\:\!bg-gray-200:hover { - --tw-bg-opacity: 1 !important; - background-color: rgb(229 231 235 / var(--tw-bg-opacity)) !important; - } - .hover\:\!bg-gray-300:hover { - --tw-bg-opacity: 1 !important; - background-color: rgb(209 213 219 / var(--tw-bg-opacity)) !important; - } - .hover\:\!bg-gray-400:hover { - --tw-bg-opacity: 1 !important; - background-color: rgb(156 163 175 / var(--tw-bg-opacity)) !important; - } - .hover\:\!text-gray-700:hover { - --tw-text-opacity: 1 !important; - color: rgb(55 65 81 / var(--tw-text-opacity)) !important; - } - .hover\:\!text-gray-800:hover { - --tw-text-opacity: 1 !important; - color: rgb(31 41 55 / var(--tw-text-opacity)) !important; - } - .hover\:\!text-gray-900:hover { - --tw-text-opacity: 1 !important; - color: rgb(17 24 39 / var(--tw-text-opacity)) !important; - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .\!bg-gray-200 { - background-color: #e5e7eb !important; - } - .\!bg-gray-300 { - background-color: #d1d5db !important; - } - .\!bg-gray-400 { - background-color: #9ca3af !important; - } - .uppercase { - text-transform: uppercase; - } - .\!text-gray-700 { - color: #374151 !important; - } - .\!text-gray-800 { - color: #1f2937 !important; - } - .\!text-gray-900 { - color: #111827 !important; - } - .hover\:\!bg-gray-200:hover { - background-color: #e5e7eb !important; - } - .hover\:\!bg-gray-300:hover { - background-color: #d1d5db !important; - } - .hover\:\!bg-gray-400:hover { - background-color: #9ca3af !important; - } - .hover\:\!text-gray-700:hover { - color: #374151 !important; - } - .hover\:\!text-gray-800:hover { - color: #1f2937 !important; - } - .hover\:\!text-gray-900:hover { - color: #111827 !important; - } - `) - }) + return run('@tailwind utilities', config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .\!bg-gray-200 { + --tw-bg-opacity: 1 !important; + background-color: rgb(229 231 235 / var(--tw-bg-opacity)) !important; + } + .\!bg-gray-300 { + --tw-bg-opacity: 1 !important; + background-color: rgb(209 213 219 / var(--tw-bg-opacity)) !important; + } + .\!bg-gray-400 { + --tw-bg-opacity: 1 !important; + background-color: rgb(156 163 175 / var(--tw-bg-opacity)) !important; + } + .uppercase { + text-transform: uppercase; + } + .\!text-gray-700 { + --tw-text-opacity: 1 !important; + color: rgb(55 65 81 / var(--tw-text-opacity)) !important; + } + .\!text-gray-800 { + --tw-text-opacity: 1 !important; + color: rgb(31 41 55 / var(--tw-text-opacity)) !important; + } + .\!text-gray-900 { + --tw-text-opacity: 1 !important; + color: rgb(17 24 39 / var(--tw-text-opacity)) !important; + } + .hover\:\!bg-gray-200:hover { + --tw-bg-opacity: 1 !important; + background-color: rgb(229 231 235 / var(--tw-bg-opacity)) !important; + } + .hover\:\!bg-gray-300:hover { + --tw-bg-opacity: 1 !important; + background-color: rgb(209 213 219 / var(--tw-bg-opacity)) !important; + } + .hover\:\!bg-gray-400:hover { + --tw-bg-opacity: 1 !important; + background-color: rgb(156 163 175 / var(--tw-bg-opacity)) !important; + } + .hover\:\!text-gray-700:hover { + --tw-text-opacity: 1 !important; + color: rgb(55 65 81 / var(--tw-text-opacity)) !important; + } + .hover\:\!text-gray-800:hover { + --tw-text-opacity: 1 !important; + color: rgb(31 41 55 / var(--tw-text-opacity)) !important; + } + .hover\:\!text-gray-900:hover { + --tw-text-opacity: 1 !important; + color: rgb(17 24 39 / var(--tw-text-opacity)) !important; + } + `) }) }) diff --git a/tests/screenAtRule.test.js b/tests/screenAtRule.test.js index 6352fcde09bd..b6f1723c3419 100644 --- a/tests/screenAtRule.test.js +++ b/tests/screenAtRule.test.js @@ -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) }) }) diff --git a/tests/shared-state.test.js b/tests/shared-state.test.js index 4791d07dc435..a3a9f287073d 100644 --- a/tests/shared-state.test.js +++ b/tests/shared-state.test.js @@ -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) }) diff --git a/tests/source-maps.test.js b/tests/source-maps.test.js index 08b60c9f52a1..1f2e258d867b 100644 --- a/tests/source-maps.test.js +++ b/tests/source-maps.test.js @@ -1,555 +1,540 @@ import postcss from 'postcss' import { parseSourceMaps } from './util/source-maps' -import { crosscheck, runWithSourceMaps as run, html, css, map } from './util/run' +import { runWithSourceMaps as run, html, css, map } from './util/run' -crosscheck(({ stable, oxide }) => { - oxide.test.todo('apply generates source maps') - stable.test('apply generates source maps', async () => { - let config = { - content: [ - { - raw: html` -
-
-
- `, - }, - ], - corePlugins: { preflight: false }, - } +test('apply generates source maps', async () => { + let config = { + content: [ + { + raw: html` +
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + } - let input = css` - .with-declaration { - background-color: red; - @apply h-4 w-4 bg-green-500; - } + let input = css` + .with-declaration { + background-color: red; + @apply h-4 w-4 bg-green-500; + } - .with-comment { - /* sourcemap will work here too */ - @apply h-4 w-4 bg-red-500; - } + .with-comment { + /* sourcemap will work here too */ + @apply h-4 w-4 bg-red-500; + } - .just-apply { - @apply h-4 w-4 bg-black; - } - ` + .just-apply { + @apply h-4 w-4 bg-black; + } + ` - let result = await run(input, config) - let { sources, annotations } = parseSourceMaps(result) + let result = await run(input, config) + let { sources, annotations } = parseSourceMaps(result) - // All CSS generated by Tailwind CSS should be annotated with source maps - // And always be able to point to the original source file - expect(sources).not.toContain('') - expect(sources.length).toBe(1) + // All CSS generated by Tailwind CSS should be annotated with source maps + // And always be able to point to the original source file + expect(sources).not.toContain('') + expect(sources.length).toBe(1) - expect(annotations).toEqual([ - '2:6 -> 2:6', - '3:8-29 -> 3:8-29', - '4:8-35 -> 4:8-20', - '4:8-35 -> 5:8-19', - '4:8-35 -> 6:8-26', - '4:8-35 -> 7:8-63', - '5:6 -> 8:6', - '7:6 -> 10:6', - '8:8-41 -> 11:8-41', - '9:8-33 -> 12:8-20', - '9:8-33 -> 13:8-19', - '9:8-33 -> 14:8-26', - '9:8-33 -> 15:8-63', - '10:6 -> 16:6', - '13:8 -> 18:6', - '13:8-31 -> 19:8-20', - '13:8-31 -> 20:8-19', - '13:8-31 -> 21:8-26', - '13:8 -> 22:8', - '13:31 -> 23:0', - ]) - }) + expect(annotations).toEqual([ + '2:4 -> 2:4', + '3:6-27 -> 3:6-27', + '4:6-33 -> 4:6-18', + '4:6-33 -> 5:6-17', + '4:6-33 -> 6:6-24', + '4:6-33 -> 7:6-61', + '5:4 -> 8:4', + '7:4 -> 10:4', + '8:6-39 -> 11:6-39', + '9:6-31 -> 12:6-18', + '9:6-31 -> 13:6-17', + '9:6-31 -> 14:6-24', + '9:6-31 -> 15:6-61', + '10:4 -> 16:4', + '13:6 -> 18:4', + '13:6-29 -> 19:6-18', + '13:6-29 -> 20:6-17', + '13:6-29 -> 21:6-24', + '13:6 -> 22:6', + '13:29 -> 23:0', + ]) +}) - oxide.test.todo('preflight + base have source maps') - stable.test('preflight + base have source maps', async () => { - let config = { - content: [], - } +test('preflight + base have source maps', async () => { + let config = { + content: [], + } - let input = css` - @tailwind base; - ` + let input = css` + @tailwind base; + ` - let result = await run(input, config) - let { sources, annotations } = parseSourceMaps(result) + let result = await run(input, config) + let { sources, annotations } = parseSourceMaps(result) - // All CSS generated by Tailwind CSS should be annotated with source maps - // And always be able to point to the original source file - expect(sources).not.toContain('') - expect(sources.length).toBe(1) + // All CSS generated by Tailwind CSS should be annotated with source maps + // And always be able to point to the original source file + expect(sources).not.toContain('') + expect(sources.length).toBe(1) - expect(annotations).toEqual([ - '2:6 -> 1:0', - '2:20-6 -> 3:1-2', - '2:20 -> 6:1', - '2:6 -> 8:0', - '2:6-20 -> 11:2-32', - '2:6-20 -> 12:2-25', - '2:6-20 -> 13:2-29', - '2:6-20 -> 14:2-31', - '2:20 -> 15:0', - '2:6 -> 17:0', - '2:6-20 -> 19:2-18', - '2:20 -> 20:0', - '2:6 -> 22:0', - '2:20 -> 30:1', - '2:6 -> 32:0', - '2:6-20 -> 34:2-26', - '2:6-20 -> 35:2-40', - '2:6-20 -> 36:2-26', - '2:6-20 -> 37:2-21', - '2:6-20 -> 38:2-137', - '2:6-20 -> 39:2-39', - '2:6-20 -> 40:2-41', - '2:6-20 -> 41:2-50', - '2:20 -> 42:0', - '2:6 -> 44:0', - '2:20 -> 47:1', - '2:6 -> 49:0', - '2:6-20 -> 50:2-19', - '2:6-20 -> 51:2-30', - '2:20 -> 52:0', - '2:6 -> 54:0', - '2:20 -> 58:1', - '2:6 -> 60:0', - '2:6-20 -> 61:2-19', - '2:6-20 -> 62:2-24', - '2:6-20 -> 63:2-31', - '2:20 -> 64:0', - '2:6 -> 66:0', - '2:20 -> 68:1', - '2:6 -> 70:0', - '2:6-20 -> 71:2-35', - '2:20 -> 72:0', - '2:6 -> 74:0', - '2:20 -> 76:1', - '2:6 -> 78:0', - '2:6-20 -> 84:2-20', - '2:6-20 -> 85:2-22', - '2:20 -> 86:0', - '2:6 -> 88:0', - '2:20 -> 90:1', - '2:6 -> 92:0', - '2:6-20 -> 93:2-16', - '2:6-20 -> 94:2-26', - '2:20 -> 95:0', - '2:6 -> 97:0', - '2:20 -> 99:1', - '2:6 -> 101:0', - '2:6-20 -> 103:2-21', - '2:20 -> 104:0', - '2:6 -> 106:0', - '2:20 -> 111:1', - '2:6 -> 113:0', - '2:6-20 -> 117:2-121', - '2:6-20 -> 118:2-39', - '2:6-20 -> 119:2-41', - '2:6-20 -> 120:2-24', - '2:20 -> 121:0', - '2:6 -> 123:0', - '2:20 -> 125:1', - '2:6 -> 127:0', - '2:6-20 -> 128:2-16', - '2:20 -> 129:0', - '2:6 -> 131:0', - '2:20 -> 133:1', - '2:6 -> 135:0', - '2:6-20 -> 137:2-16', - '2:6-20 -> 138:2-16', - '2:6-20 -> 139:2-20', - '2:6-20 -> 140:2-26', - '2:20 -> 141:0', - '2:6 -> 143:0', - '2:6-20 -> 144:2-17', - '2:20 -> 145:0', - '2:6 -> 147:0', - '2:6-20 -> 148:2-13', - '2:20 -> 149:0', - '2:6 -> 151:0', - '2:20 -> 155:1', - '2:6 -> 157:0', - '2:6-20 -> 158:2-24', - '2:6-20 -> 159:2-31', - '2:6-20 -> 160:2-35', - '2:20 -> 161:0', - '2:6 -> 163:0', - '2:20 -> 167:1', - '2:6 -> 169:0', - '2:6-20 -> 174:2-30', - '2:6-20 -> 175:2-40', - '2:6-20 -> 176:2-42', - '2:6-20 -> 177:2-25', - '2:6-20 -> 178:2-30', - '2:6-20 -> 179:2-30', - '2:6-20 -> 180:2-24', - '2:6-20 -> 181:2-19', - '2:6-20 -> 182:2-20', - '2:20 -> 183:0', - '2:6 -> 185:0', - '2:20 -> 187:1', - '2:6 -> 189:0', - '2:6-20 -> 191:2-22', - '2:20 -> 192:0', - '2:6 -> 194:0', - '2:20 -> 197:1', - '2:6 -> 199:0', - '2:6-20 -> 203:2-36', - '2:6-20 -> 204:2-39', - '2:6-20 -> 205:2-32', - '2:20 -> 206:0', - '2:6 -> 208:0', - '2:20 -> 210:1', - '2:6 -> 212:0', - '2:6-20 -> 213:2-15', - '2:20 -> 214:0', - '2:6 -> 216:0', - '2:20 -> 218:1', - '2:6 -> 220:0', - '2:6-20 -> 221:2-18', - '2:20 -> 222:0', - '2:6 -> 224:0', - '2:20 -> 226:1', - '2:6 -> 228:0', - '2:6-20 -> 229:2-26', - '2:20 -> 230:0', - '2:6 -> 232:0', - '2:20 -> 234:1', - '2:6 -> 236:0', - '2:6-20 -> 238:2-14', - '2:20 -> 239:0', - '2:6 -> 241:0', - '2:20 -> 244:1', - '2:6 -> 246:0', - '2:6-20 -> 247:2-39', - '2:6-20 -> 248:2-30', - '2:20 -> 249:0', - '2:6 -> 251:0', - '2:20 -> 253:1', - '2:6 -> 255:0', - '2:6-20 -> 256:2-26', - '2:20 -> 257:0', - '2:6 -> 259:0', - '2:20 -> 262:1', - '2:6 -> 264:0', - '2:6-20 -> 265:2-36', - '2:6-20 -> 266:2-23', - '2:20 -> 267:0', - '2:6 -> 269:0', - '2:20 -> 271:1', - '2:6 -> 273:0', - '2:6-20 -> 274:2-20', - '2:20 -> 275:0', - '2:6 -> 277:0', - '2:20 -> 279:1', - '2:6 -> 281:0', - '2:6-20 -> 294:2-11', - '2:20 -> 295:0', - '2:6 -> 297:0', - '2:6-20 -> 298:2-11', - '2:6-20 -> 299:2-12', - '2:20 -> 300:0', - '2:6 -> 302:0', - '2:6-20 -> 303:2-12', - '2:20 -> 304:0', - '2:6 -> 306:0', - '2:6-20 -> 309:2-18', - '2:6-20 -> 310:2-11', - '2:6-20 -> 311:2-12', - '2:20 -> 312:0', - '2:6 -> 314:0', - '2:20 -> 316:1', - '2:6 -> 317:0', - '2:6-20 -> 318:2-12', - '2:20 -> 319:0', - '2:6 -> 321:0', - '2:20 -> 323:1', - '2:6 -> 325:0', - '2:6-20 -> 326:2-18', - '2:20 -> 327:0', - '2:6 -> 329:0', - '2:20 -> 332:1', - '2:6 -> 334:0', - '2:6-20 -> 336:2-20', - '2:6-20 -> 337:2-24', - '2:20 -> 338:0', - '2:6 -> 340:0', - '2:20 -> 342:1', - '2:6 -> 344:0', - '2:6-20 -> 346:2-17', - '2:20 -> 347:0', - '2:6 -> 349:0', - '2:20 -> 351:1', - '2:6 -> 352:0', - '2:6-20 -> 353:2-17', - '2:20 -> 354:0', - '2:6 -> 356:0', - '2:20 -> 360:1', - '2:6 -> 362:0', - '2:6-20 -> 370:2-24', - '2:6-20 -> 371:2-32', - '2:20 -> 372:0', - '2:6 -> 374:0', - '2:20 -> 376:1', - '2:6 -> 378:0', - '2:6-20 -> 380:2-17', - '2:6-20 -> 381:2-14', - '2:20 -> 382:0', - '2:6-20 -> 384:0-72', - '2:6 -> 385:0', - '2:6-20 -> 386:2-15', - '2:20 -> 387:0', - '2:6 -> 389:0', - '2:6-20 -> 390:2-26', - '2:6-20 -> 391:2-26', - '2:6-20 -> 392:2-21', - '2:6-20 -> 393:2-21', - '2:6-20 -> 394:2-16', - '2:6-20 -> 395:2-16', - '2:6-20 -> 396:2-16', - '2:6-20 -> 397:2-17', - '2:6-20 -> 398:2-17', - '2:6-20 -> 399:2-15', - '2:6-20 -> 400:2-15', - '2:6-20 -> 401:2-20', - '2:6-20 -> 402:2-40', - '2:6-20 -> 403:2-32', - '2:6-20 -> 404:2-31', - '2:6-20 -> 405:2-30', - '2:6-20 -> 406:2-17', - '2:6-20 -> 407:2-22', - '2:6-20 -> 408:2-24', - '2:6-20 -> 409:2-25', - '2:6-20 -> 410:2-26', - '2:6-20 -> 411:2-20', - '2:6-20 -> 412:2-29', - '2:6-20 -> 413:2-30', - '2:6-20 -> 414:2-40', - '2:6-20 -> 415:2-36', - '2:6-20 -> 416:2-29', - '2:6-20 -> 417:2-24', - '2:6-20 -> 418:2-32', - '2:6-20 -> 419:2-14', - '2:6-20 -> 420:2-20', - '2:6-20 -> 421:2-18', - '2:6-20 -> 422:2-19', - '2:6-20 -> 423:2-20', - '2:6-20 -> 424:2-16', - '2:6-20 -> 425:2-18', - '2:6-20 -> 426:2-15', - '2:6-20 -> 427:2-21', - '2:6-20 -> 428:2-23', - '2:6-20 -> 429:2-29', - '2:6-20 -> 430:2-27', - '2:6-20 -> 431:2-28', - '2:6-20 -> 432:2-29', - '2:6-20 -> 433:2-25', - '2:6-20 -> 434:2-26', - '2:6-20 -> 435:2-27', - '2:6-20 -> 436:2-24', - '2:6-20 -> 437:2-22', - '2:6-20 -> 438:2-24', - '2:6-20 -> 439:2-23', - '2:6 -> 440:2', - '2:20 -> 441:0', - '2:6 -> 443:0', - '2:6-20 -> 444:2-26', - '2:6-20 -> 445:2-26', - '2:6-20 -> 446:2-21', - '2:6-20 -> 447:2-21', - '2:6-20 -> 448:2-16', - '2:6-20 -> 449:2-16', - '2:6-20 -> 450:2-16', - '2:6-20 -> 451:2-17', - '2:6-20 -> 452:2-17', - '2:6-20 -> 453:2-15', - '2:6-20 -> 454:2-15', - '2:6-20 -> 455:2-20', - '2:6-20 -> 456:2-40', - '2:6-20 -> 457:2-32', - '2:6-20 -> 458:2-31', - '2:6-20 -> 459:2-30', - '2:6-20 -> 460:2-17', - '2:6-20 -> 461:2-22', - '2:6-20 -> 462:2-24', - '2:6-20 -> 463:2-25', - '2:6-20 -> 464:2-26', - '2:6-20 -> 465:2-20', - '2:6-20 -> 466:2-29', - '2:6-20 -> 467:2-30', - '2:6-20 -> 468:2-40', - '2:6-20 -> 469:2-36', - '2:6-20 -> 470:2-29', - '2:6-20 -> 471:2-24', - '2:6-20 -> 472:2-32', - '2:6-20 -> 473:2-14', - '2:6-20 -> 474:2-20', - '2:6-20 -> 475:2-18', - '2:6-20 -> 476:2-19', - '2:6-20 -> 477:2-20', - '2:6-20 -> 478:2-16', - '2:6-20 -> 479:2-18', - '2:6-20 -> 480:2-15', - '2:6-20 -> 481:2-21', - '2:6-20 -> 482:2-23', - '2:6-20 -> 483:2-29', - '2:6-20 -> 484:2-27', - '2:6-20 -> 485:2-28', - '2:6-20 -> 486:2-29', - '2:6-20 -> 487:2-25', - '2:6-20 -> 488:2-26', - '2:6-20 -> 489:2-27', - '2:6-20 -> 490:2-24', - '2:6-20 -> 491:2-22', - '2:6-20 -> 492:2-24', - '2:6-20 -> 493:2-23', - '2:6 -> 494:2', - '2:20 -> 495:0', - ]) - }) + expect(annotations).toEqual([ + '2:4 -> 1:0', + '2:18-4 -> 3:1-2', + '2:18 -> 6:1', + '2:4 -> 8:0', + '2:4-18 -> 11:2-32', + '2:4-18 -> 12:2-25', + '2:4-18 -> 13:2-29', + '2:4-18 -> 14:2-31', + '2:18 -> 15:0', + '2:4 -> 17:0', + '2:4-18 -> 19:2-18', + '2:18 -> 20:0', + '2:4 -> 22:0', + '2:18 -> 30:1', + '2:4 -> 32:0', + '2:4-18 -> 34:2-26', + '2:4-18 -> 35:2-40', + '2:4-18 -> 36:2-26', + '2:4-18 -> 37:2-21', + '2:4-18 -> 38:2-137', + '2:4-18 -> 39:2-39', + '2:4-18 -> 40:2-41', + '2:4-18 -> 41:2-50', + '2:18 -> 42:0', + '2:4 -> 44:0', + '2:18 -> 47:1', + '2:4 -> 49:0', + '2:4-18 -> 50:2-19', + '2:4-18 -> 51:2-30', + '2:18 -> 52:0', + '2:4 -> 54:0', + '2:18 -> 58:1', + '2:4 -> 60:0', + '2:4-18 -> 61:2-19', + '2:4-18 -> 62:2-24', + '2:4-18 -> 63:2-31', + '2:18 -> 64:0', + '2:4 -> 66:0', + '2:18 -> 68:1', + '2:4 -> 70:0', + '2:4-18 -> 71:2-35', + '2:18 -> 72:0', + '2:4 -> 74:0', + '2:18 -> 76:1', + '2:4 -> 78:0', + '2:4-18 -> 84:2-20', + '2:4-18 -> 85:2-22', + '2:18 -> 86:0', + '2:4 -> 88:0', + '2:18 -> 90:1', + '2:4 -> 92:0', + '2:4-18 -> 93:2-16', + '2:4-18 -> 94:2-26', + '2:18 -> 95:0', + '2:4 -> 97:0', + '2:18 -> 99:1', + '2:4 -> 101:0', + '2:4-18 -> 103:2-21', + '2:18 -> 104:0', + '2:4 -> 106:0', + '2:18 -> 111:1', + '2:4 -> 113:0', + '2:4-18 -> 117:2-121', + '2:4-18 -> 118:2-39', + '2:4-18 -> 119:2-41', + '2:4-18 -> 120:2-24', + '2:18 -> 121:0', + '2:4 -> 123:0', + '2:18 -> 125:1', + '2:4 -> 127:0', + '2:4-18 -> 128:2-16', + '2:18 -> 129:0', + '2:4 -> 131:0', + '2:18 -> 133:1', + '2:4 -> 135:0', + '2:4-18 -> 137:2-16', + '2:4-18 -> 138:2-16', + '2:4-18 -> 139:2-20', + '2:4-18 -> 140:2-26', + '2:18 -> 141:0', + '2:4 -> 143:0', + '2:4-18 -> 144:2-17', + '2:18 -> 145:0', + '2:4 -> 147:0', + '2:4-18 -> 148:2-13', + '2:18 -> 149:0', + '2:4 -> 151:0', + '2:18 -> 155:1', + '2:4 -> 157:0', + '2:4-18 -> 158:2-24', + '2:4-18 -> 159:2-31', + '2:4-18 -> 160:2-35', + '2:18 -> 161:0', + '2:4 -> 163:0', + '2:18 -> 167:1', + '2:4 -> 169:0', + '2:4-18 -> 174:2-30', + '2:4-18 -> 175:2-40', + '2:4-18 -> 176:2-42', + '2:4-18 -> 177:2-25', + '2:4-18 -> 178:2-30', + '2:4-18 -> 179:2-30', + '2:4-18 -> 180:2-24', + '2:4-18 -> 181:2-19', + '2:4-18 -> 182:2-20', + '2:18 -> 183:0', + '2:4 -> 185:0', + '2:18 -> 187:1', + '2:4 -> 189:0', + '2:4-18 -> 191:2-22', + '2:18 -> 192:0', + '2:4 -> 194:0', + '2:18 -> 197:1', + '2:4 -> 199:0', + '2:4-18 -> 203:2-36', + '2:4-18 -> 204:2-39', + '2:4-18 -> 205:2-32', + '2:18 -> 206:0', + '2:4 -> 208:0', + '2:18 -> 210:1', + '2:4 -> 212:0', + '2:4-18 -> 213:2-15', + '2:18 -> 214:0', + '2:4 -> 216:0', + '2:18 -> 218:1', + '2:4 -> 220:0', + '2:4-18 -> 221:2-18', + '2:18 -> 222:0', + '2:4 -> 224:0', + '2:18 -> 226:1', + '2:4 -> 228:0', + '2:4-18 -> 229:2-26', + '2:18 -> 230:0', + '2:4 -> 232:0', + '2:18 -> 234:1', + '2:4 -> 236:0', + '2:4-18 -> 238:2-14', + '2:18 -> 239:0', + '2:4 -> 241:0', + '2:18 -> 244:1', + '2:4 -> 246:0', + '2:4-18 -> 247:2-39', + '2:4-18 -> 248:2-30', + '2:18 -> 249:0', + '2:4 -> 251:0', + '2:18 -> 253:1', + '2:4 -> 255:0', + '2:4-18 -> 256:2-26', + '2:18 -> 257:0', + '2:4 -> 259:0', + '2:18 -> 262:1', + '2:4 -> 264:0', + '2:4-18 -> 265:2-36', + '2:4-18 -> 266:2-23', + '2:18 -> 267:0', + '2:4 -> 269:0', + '2:18 -> 271:1', + '2:4 -> 273:0', + '2:4-18 -> 274:2-20', + '2:18 -> 275:0', + '2:4 -> 277:0', + '2:18 -> 279:1', + '2:4 -> 281:0', + '2:4-18 -> 294:2-11', + '2:18 -> 295:0', + '2:4 -> 297:0', + '2:4-18 -> 298:2-11', + '2:4-18 -> 299:2-12', + '2:18 -> 300:0', + '2:4 -> 302:0', + '2:4-18 -> 303:2-12', + '2:18 -> 304:0', + '2:4 -> 306:0', + '2:4-18 -> 309:2-18', + '2:4-18 -> 310:2-11', + '2:4-18 -> 311:2-12', + '2:18 -> 312:0', + '2:4 -> 314:0', + '2:18 -> 316:1', + '2:4 -> 317:0', + '2:4-18 -> 318:2-12', + '2:18 -> 319:0', + '2:4 -> 321:0', + '2:18 -> 323:1', + '2:4 -> 325:0', + '2:4-18 -> 326:2-18', + '2:18 -> 327:0', + '2:4 -> 329:0', + '2:18 -> 332:1', + '2:4 -> 334:0', + '2:4-18 -> 336:2-20', + '2:4-18 -> 337:2-24', + '2:18 -> 338:0', + '2:4 -> 340:0', + '2:18 -> 342:1', + '2:4 -> 344:0', + '2:4-18 -> 346:2-17', + '2:18 -> 347:0', + '2:4 -> 349:0', + '2:18 -> 351:1', + '2:4 -> 352:0', + '2:4-18 -> 353:2-17', + '2:18 -> 354:0', + '2:4 -> 356:0', + '2:18 -> 360:1', + '2:4 -> 362:0', + '2:4-18 -> 370:2-24', + '2:4-18 -> 371:2-32', + '2:18 -> 372:0', + '2:4 -> 374:0', + '2:18 -> 376:1', + '2:4 -> 378:0', + '2:4-18 -> 380:2-17', + '2:4-18 -> 381:2-14', + '2:18 -> 382:0', + '2:4-18 -> 384:0-72', + '2:4 -> 385:0', + '2:4-18 -> 386:2-15', + '2:18 -> 387:0', + '2:4 -> 389:0', + '2:4-18 -> 390:2-26', + '2:4-18 -> 391:2-26', + '2:4-18 -> 392:2-21', + '2:4-18 -> 393:2-21', + '2:4-18 -> 394:2-16', + '2:4-18 -> 395:2-16', + '2:4-18 -> 396:2-16', + '2:4-18 -> 397:2-17', + '2:4-18 -> 398:2-17', + '2:4-18 -> 399:2-15', + '2:4-18 -> 400:2-15', + '2:4-18 -> 401:2-20', + '2:4-18 -> 402:2-40', + '2:4-18 -> 403:2-32', + '2:4-18 -> 404:2-31', + '2:4-18 -> 405:2-30', + '2:4-18 -> 406:2-17', + '2:4-18 -> 407:2-22', + '2:4-18 -> 408:2-24', + '2:4-18 -> 409:2-25', + '2:4-18 -> 410:2-26', + '2:4-18 -> 411:2-20', + '2:4-18 -> 412:2-29', + '2:4-18 -> 413:2-30', + '2:4-18 -> 414:2-40', + '2:4-18 -> 415:2-36', + '2:4-18 -> 416:2-29', + '2:4-18 -> 417:2-24', + '2:4-18 -> 418:2-32', + '2:4-18 -> 419:2-14', + '2:4-18 -> 420:2-20', + '2:4-18 -> 421:2-18', + '2:4-18 -> 422:2-19', + '2:4-18 -> 423:2-20', + '2:4-18 -> 424:2-16', + '2:4-18 -> 425:2-18', + '2:4-18 -> 426:2-15', + '2:4-18 -> 427:2-21', + '2:4-18 -> 428:2-23', + '2:4-18 -> 429:2-29', + '2:4-18 -> 430:2-27', + '2:4-18 -> 431:2-28', + '2:4-18 -> 432:2-29', + '2:4-18 -> 433:2-25', + '2:4-18 -> 434:2-26', + '2:4-18 -> 435:2-27', + '2:4-18 -> 436:2-24', + '2:4-18 -> 437:2-22', + '2:4-18 -> 438:2-24', + '2:4-18 -> 439:2-23', + '2:4 -> 440:2', + '2:18 -> 441:0', + '2:4 -> 443:0', + '2:4-18 -> 444:2-26', + '2:4-18 -> 445:2-26', + '2:4-18 -> 446:2-21', + '2:4-18 -> 447:2-21', + '2:4-18 -> 448:2-16', + '2:4-18 -> 449:2-16', + '2:4-18 -> 450:2-16', + '2:4-18 -> 451:2-17', + '2:4-18 -> 452:2-17', + '2:4-18 -> 453:2-15', + '2:4-18 -> 454:2-15', + '2:4-18 -> 455:2-20', + '2:4-18 -> 456:2-40', + '2:4-18 -> 457:2-32', + '2:4-18 -> 458:2-31', + '2:4-18 -> 459:2-30', + '2:4-18 -> 460:2-17', + '2:4-18 -> 461:2-22', + '2:4-18 -> 462:2-24', + '2:4-18 -> 463:2-25', + '2:4-18 -> 464:2-26', + '2:4-18 -> 465:2-20', + '2:4-18 -> 466:2-29', + '2:4-18 -> 467:2-30', + '2:4-18 -> 468:2-40', + '2:4-18 -> 469:2-36', + '2:4-18 -> 470:2-29', + '2:4-18 -> 471:2-24', + '2:4-18 -> 472:2-32', + '2:4-18 -> 473:2-14', + '2:4-18 -> 474:2-20', + '2:4-18 -> 475:2-18', + '2:4-18 -> 476:2-19', + '2:4-18 -> 477:2-20', + '2:4-18 -> 478:2-16', + '2:4-18 -> 479:2-18', + '2:4-18 -> 480:2-15', + '2:4-18 -> 481:2-21', + '2:4-18 -> 482:2-23', + '2:4-18 -> 483:2-29', + '2:4-18 -> 484:2-27', + '2:4-18 -> 485:2-28', + '2:4-18 -> 486:2-29', + '2:4-18 -> 487:2-25', + '2:4-18 -> 488:2-26', + '2:4-18 -> 489:2-27', + '2:4-18 -> 490:2-24', + '2:4-18 -> 491:2-22', + '2:4-18 -> 492:2-24', + '2:4-18 -> 493:2-23', + '2:4 -> 494:2', + '2:18 -> 495:0', + ]) +}) - oxide.test.todo('utilities have source maps') - stable.test('utilities have source maps', async () => { - let config = { - content: [{ raw: `text-red-500` }], - } +test('utilities have source maps', async () => { + let config = { + content: [{ raw: `text-red-500` }], + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - let result = await run(input, config) - let { sources, annotations } = parseSourceMaps(result) + let result = await run(input, config) + let { sources, annotations } = parseSourceMaps(result) - // All CSS generated by Tailwind CSS should be annotated with source maps - // And always be able to point to the original source file - expect(sources).not.toContain('') - expect(sources.length).toBe(1) + // All CSS generated by Tailwind CSS should be annotated with source maps + // And always be able to point to the original source file + expect(sources).not.toContain('') + expect(sources.length).toBe(1) - expect(annotations).toStrictEqual([ - '2:6 -> 1:0', - '2:6-25 -> 2:4-24', - '2:6 -> 3:4', - '2:25 -> 4:0', - ]) - }) + expect(annotations).toStrictEqual(['2:4 -> 1:0', '2:4-23 -> 2:4-24', '2:4 -> 3:4', '2:23 -> 4:0']) +}) - oxide.test.todo('components have source maps') - stable.test('components have source maps', async () => { - let config = { - content: [{ raw: `container` }], - } +test('components have source maps', async () => { + let config = { + content: [{ raw: `container` }], + } - let input = css` - @tailwind components; - ` + let input = css` + @tailwind components; + ` - let result = await run(input, config) - let { sources, annotations } = parseSourceMaps(result) + let result = await run(input, config) + let { sources, annotations } = parseSourceMaps(result) - // All CSS generated by Tailwind CSS should be annotated with source maps - // And always be able to point to the original source file - expect(sources).not.toContain('') - expect(sources.length).toBe(1) + // All CSS generated by Tailwind CSS should be annotated with source maps + // And always be able to point to the original source file + expect(sources).not.toContain('') + expect(sources.length).toBe(1) - expect(annotations).toEqual([ - '2:6 -> 1:0', - '2:6 -> 2:4', - '2:26 -> 3:0', - '2:6 -> 4:0', - '2:6 -> 5:4', - '2:6 -> 6:8', - '2:26 -> 7:4', - '2:26 -> 8:0', - '2:6 -> 9:0', - '2:6 -> 10:4', - '2:6 -> 11:8', - '2:26 -> 12:4', - '2:26 -> 13:0', - '2:6 -> 14:0', - '2:6 -> 15:4', - '2:6 -> 16:8', - '2:26 -> 17:4', - '2:26 -> 18:0', - '2:6 -> 19:0', - '2:6 -> 20:4', - '2:6 -> 21:8', - '2:26 -> 22:4', - '2:26 -> 23:0', - '2:6 -> 24:0', - '2:6 -> 25:4', - '2:6 -> 26:8', - '2:26 -> 27:4', - '2:26 -> 28:0', - ]) - }) + expect(annotations).toEqual([ + '2:4 -> 1:0', + '2:4 -> 2:4', + '2:24 -> 3:0', + '2:4 -> 4:0', + '2:4 -> 5:4', + '2:4 -> 6:8', + '2:24 -> 7:4', + '2:24 -> 8:0', + '2:4 -> 9:0', + '2:4 -> 10:4', + '2:4 -> 11:8', + '2:24 -> 12:4', + '2:24 -> 13:0', + '2:4 -> 14:0', + '2:4 -> 15:4', + '2:4 -> 16:8', + '2:24 -> 17:4', + '2:24 -> 18:0', + '2:4 -> 19:0', + '2:4 -> 20:4', + '2:4 -> 21:8', + '2:24 -> 22:4', + '2:24 -> 23:0', + '2:4 -> 24:0', + '2:4 -> 25:4', + '2:4 -> 26:8', + '2:24 -> 27:4', + '2:24 -> 28:0', + ]) +}) - oxide.test.todo('source maps for layer rules are not rewritten to point to @tailwind directives') - stable.test( - 'source maps for layer rules are not rewritten to point to @tailwind directives', - async () => { - let config = { - content: [{ raw: `font-normal foo hover:foo lg:foo` }], - } +test('source maps for layer rules are not rewritten to point to @tailwind directives', async () => { + let config = { + content: [{ raw: `font-normal foo hover:foo lg:foo` }], + } - let utilitiesFile = postcss.parse( - css` - @tailwind utilities; - `, - { from: 'components.css', map: { prev: map } } - ) + let utilitiesFile = postcss.parse( + css` + @tailwind utilities; + `, + { from: 'components.css', map: { prev: map } } + ) - let mainCssFile = postcss.parse( - css` - @layer utilities { - .foo { - background-color: red; - } - } - `, - { from: 'input.css', map: { prev: map } } - ) + let mainCssFile = postcss.parse( + css` + @layer utilities { + .foo { + background-color: red; + } + } + `, + { from: 'input.css', map: { prev: map } } + ) - // Just pretend that there's an @import in `mainCssFile` that imports the nodes from `utilitiesFile` - let input = postcss.root({ - nodes: [...utilitiesFile.nodes, ...mainCssFile.nodes], - source: mainCssFile.source, - }) + // Just pretend that there's an @import in `mainCssFile` that imports the nodes from `utilitiesFile` + let input = postcss.root({ + nodes: [...utilitiesFile.nodes, ...mainCssFile.nodes], + source: mainCssFile.source, + }) - let result = await run(input, config) + let result = await run(input, config) - let { sources, annotations } = parseSourceMaps(result) + let { sources, annotations } = parseSourceMaps(result) - // All CSS generated by Tailwind CSS should be annotated with source maps - // And always be able to point to the original source file - expect(sources).not.toContain('') + // All CSS generated by Tailwind CSS should be annotated with source maps + // And always be able to point to the original source file + expect(sources).not.toContain('') - // And we should see that the source map for the layer rule is not rewritten - // to point to the @tailwind directive but instead points to the original - expect(sources.length).toBe(2) - expect(sources).toEqual(['components.css', 'input.css']) + // And we should see that the source map for the layer rule is not rewritten + // to point to the @tailwind directive but instead points to the original + expect(sources.length).toBe(2) + expect(sources).toEqual(['components.css', 'input.css']) - expect(annotations).toEqual([ - '2:10 -> 1:0', - '2:10 -> 2:14', - '2:29 -> 3:0', - '3:12 -> 4:12', - '4:14-35 -> 5:14-35', - '5:12 -> 6:12', - '3:12 -> 7:12', - '4:14-35 -> 8:14-35', - '5:12 -> 9:12', - '1:0 -> 10:12', - '3:12 -> 11:12', - '4:14-35 -> 12:14-35', - '5:12 -> 13:12', - '1:0 -> 14:0', - ]) - } - ) + expect(annotations).toEqual([ + '2:6 -> 1:0', + '2:6 -> 2:10', + '2:25 -> 3:0', + '3:8 -> 4:8', + '4:10-31 -> 5:10-31', + '5:8 -> 6:8', + '3:8 -> 7:8', + '4:10-31 -> 8:10-31', + '5:8 -> 9:8', + '1:0 -> 10:8', + '3:8 -> 11:8', + '4:10-31 -> 12:10-31', + '5:8 -> 13:8', + '1:0 -> 14:0', + ]) }) diff --git a/tests/syntax-lit-html.test.js b/tests/syntax-lit-html.test.js index 952a9b969cdc..a3bdfebda160 100644 --- a/tests/syntax-lit-html.test.js +++ b/tests/syntax-lit-html.test.js @@ -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\`\`;`, - }, - ], - corePlugins: { preflight: false }, - theme: {}, - plugins: [], - } +test('it detects classes in lit-html templates', () => { + let config = { + content: [ + { + raw: `html\`\`;`, + }, + ], + 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)); + } + `) }) }) diff --git a/tests/syntax-svelte.test.js b/tests/syntax-svelte.test.js index be2c33e8bd8e..65678857be27 100644 --- a/tests/syntax-svelte.test.js +++ b/tests/syntax-svelte.test.js @@ -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: ` - +test('using raw with svelte extension', () => { + let config = { + content: [ + { + raw: ` + - + - + `, - 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)); } - `) - }) + } + `) }) }) diff --git a/tests/tailwind-screens.test.js b/tests/tailwind-screens.test.js index f968fd0e340a..009c02bc3762 100644 --- a/tests/tailwind-screens.test.js +++ b/tests/tailwind-screens.test.js @@ -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`
` }], +test('class variants are inserted at `@tailwind variants`', async () => { + let config = { + content: [{ raw: html`
` }], + } + + 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; - } - `) - }) + } + .foo { + color: #000; + } + `) }) +}) - test('`@tailwind screens` works as an alias for `@tailwind variants`', async () => { - let config = { - content: [{ raw: html`
` }], +test('`@tailwind screens` works as an alias for `@tailwind variants`', async () => { + let config = { + content: [{ raw: html`
` }], + } + + let input = css` + @tailwind utilities; + @tailwind screens; + .foo { + color: black; } + ` - 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; } - ` - - 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; - } - `) - }) + } + .foo { + color: #000; + } + `) }) }) diff --git a/tests/to-path.test.js b/tests/to-path.test.js index 1e6021ba9b1e..8737194ed40e 100644 --- a/tests/to-path.test.js +++ b/tests/to-path.test.js @@ -1,21 +1,18 @@ import { toPath } from '../src/util/toPath' -import { crosscheck } from './util/run' -crosscheck(() => { - it('should keep an array as an array', () => { - let input = ['a', 'b', '0', 'c'] +it('should keep an array as an array', () => { + let input = ['a', 'b', '0', 'c'] - expect(toPath(input)).toBe(input) - }) + expect(toPath(input)).toBe(input) +}) - it.each` - input | output - ${'a.b.c'} | ${['a', 'b', 'c']} - ${'a[0].b.c'} | ${['a', '0', 'b', 'c']} - ${'.a'} | ${['a']} - ${'[].a'} | ${['a']} - ${'a[1.5][b][c]'} | ${['a', '1.5', 'b', 'c']} - `('should convert "$input" to "$output"', ({ input, output }) => { - expect(toPath(input)).toEqual(output) - }) +it.each` + input | output + ${'a.b.c'} | ${['a', 'b', 'c']} + ${'a[0].b.c'} | ${['a', '0', 'b', 'c']} + ${'.a'} | ${['a']} + ${'[].a'} | ${['a']} + ${'a[1.5][b][c]'} | ${['a', '1.5', 'b', 'c']} +`('should convert "$input" to "$output"', ({ input, output }) => { + expect(toPath(input)).toEqual(output) }) diff --git a/tests/util/apply-important-selector.test.js b/tests/util/apply-important-selector.test.js index ddbb86a1c08e..f5d3697cf88c 100644 --- a/tests/util/apply-important-selector.test.js +++ b/tests/util/apply-important-selector.test.js @@ -1,28 +1,25 @@ -import { crosscheck } from '../util/run' import { applyImportantSelector } from '../../src/util/applyImportantSelector' -crosscheck(() => { - it.each` - before | after - ${'.foo'} | ${'#app :is(.foo)'} - ${'.foo .bar'} | ${'#app :is(.foo .bar)'} - ${'.foo:hover'} | ${'#app :is(.foo:hover)'} - ${'.foo .bar:hover'} | ${'#app :is(.foo .bar:hover)'} - ${'.foo::before'} | ${'#app :is(.foo)::before'} - ${'.foo::before'} | ${'#app :is(.foo)::before'} - ${'.foo::file-selector-button'} | ${'#app :is(.foo)::file-selector-button'} - ${'.foo::-webkit-progress-bar'} | ${'#app :is(.foo)::-webkit-progress-bar'} - ${'.foo:hover::before'} | ${'#app :is(.foo:hover)::before'} - ${':is(:where(.dark) :is(:where([dir="rtl"]) .foo::before))'} | ${'#app :is(:where(.dark) :is(:where([dir="rtl"]) .foo))::before'} - ${':is(:where(.dark) .foo) .bar'} | ${'#app :is(:is(:where(.dark) .foo) .bar)'} - ${':is(.foo) :is(.bar)'} | ${'#app :is(:is(.foo) :is(.bar))'} - ${':is(.foo)::before'} | ${'#app :is(.foo)::before'} - ${'.foo:before'} | ${'#app :is(.foo):before'} - ${'.foo::some-uknown-pseudo'} | ${'#app :is(.foo)::some-uknown-pseudo'} - ${'.foo::some-uknown-pseudo:hover'} | ${'#app :is(.foo)::some-uknown-pseudo:hover'} - ${'.foo:focus::some-uknown-pseudo:hover'} | ${'#app :is(.foo:focus)::some-uknown-pseudo:hover'} - ${'.foo:hover::some-uknown-pseudo:focus'} | ${'#app :is(.foo:hover)::some-uknown-pseudo:focus'} - `('should generate "$after" from "$before"', ({ before, after }) => { - expect(applyImportantSelector(before, '#app')).toEqual(after) - }) +it.each` + before | after + ${'.foo'} | ${'#app :is(.foo)'} + ${'.foo .bar'} | ${'#app :is(.foo .bar)'} + ${'.foo:hover'} | ${'#app :is(.foo:hover)'} + ${'.foo .bar:hover'} | ${'#app :is(.foo .bar:hover)'} + ${'.foo::before'} | ${'#app :is(.foo)::before'} + ${'.foo::before'} | ${'#app :is(.foo)::before'} + ${'.foo::file-selector-button'} | ${'#app :is(.foo)::file-selector-button'} + ${'.foo::-webkit-progress-bar'} | ${'#app :is(.foo)::-webkit-progress-bar'} + ${'.foo:hover::before'} | ${'#app :is(.foo:hover)::before'} + ${':is(:where(.dark) :is(:where([dir="rtl"]) .foo::before))'} | ${'#app :is(:where(.dark) :is(:where([dir="rtl"]) .foo))::before'} + ${':is(:where(.dark) .foo) .bar'} | ${'#app :is(:is(:where(.dark) .foo) .bar)'} + ${':is(.foo) :is(.bar)'} | ${'#app :is(:is(.foo) :is(.bar))'} + ${':is(.foo)::before'} | ${'#app :is(.foo)::before'} + ${'.foo:before'} | ${'#app :is(.foo):before'} + ${'.foo::some-uknown-pseudo'} | ${'#app :is(.foo)::some-uknown-pseudo'} + ${'.foo::some-uknown-pseudo:hover'} | ${'#app :is(.foo)::some-uknown-pseudo:hover'} + ${'.foo:focus::some-uknown-pseudo:hover'} | ${'#app :is(.foo:focus)::some-uknown-pseudo:hover'} + ${'.foo:hover::some-uknown-pseudo:focus'} | ${'#app :is(.foo:hover)::some-uknown-pseudo:focus'} +`('should generate "$after" from "$before"', ({ before, after }) => { + expect(applyImportantSelector(before, '#app')).toEqual(after) }) diff --git a/tests/util/crosscheck.test.js b/tests/util/crosscheck.test.js deleted file mode 100644 index 032473386660..000000000000 --- a/tests/util/crosscheck.test.js +++ /dev/null @@ -1,18 +0,0 @@ -import { crosscheck } from './run' - -crosscheck(({ stable, oxide, engine }) => { - stable.test('should run on stable', () => { - expect(engine.stable).toBe(true) - expect(engine.oxide).toBe(false) - }) - oxide.test('should run on oxide', () => { - expect(engine.stable).toBe(false) - expect(engine.oxide).toBe(true) - }) - test('should run on both', () => { - oxide.expect(engine.oxide).toBe(true) - oxide.expect(engine.stable).toBe(false) - stable.expect(engine.oxide).toBe(false) - stable.expect(engine.stable).toBe(true) - }) -}) diff --git a/tests/util/run.js b/tests/util/run.js index 6a90f388ace1..2d458942e2b2 100644 --- a/tests/util/run.js +++ b/tests/util/run.js @@ -1,7 +1,6 @@ import path from 'path' import postcss from 'postcss' import tailwind from '../../src' -import { env } from '../../src/lib/sharedState' export * from './strings' export * from './defaults' @@ -14,8 +13,6 @@ export let map = JSON.stringify({ mappings: '', }) -globalThis.__OXIDE__ = env.ENGINE === 'oxide' - export function run(input, config, plugin = tailwind) { let { currentTestName } = expect.getState() @@ -34,60 +31,3 @@ export function runWithSourceMaps(input, config, plugin = tailwind) { }, }) } - -let nullTest = Object.assign(function () {}, { - skip: () => {}, - only: () => {}, - each: () => () => {}, - todo: () => {}, -}) -let nullProxy = new Proxy( - { - test: nullTest, - it: nullTest, - xit: nullTest.skip, - fit: nullTest.only, - xdescribe: nullTest.skip, - fdescribe: nullTest.only, - }, - { - get(target, prop, _receiver) { - if (prop in target) { - return target[prop] - } - return Object.assign(() => { - return nullProxy - }, nullProxy) - }, - } -) - -/** - * @typedef {object} CrossCheck - * @property {typeof import('@jest/globals')} oxide - * @property {typeof import('@jest/globals')} stable - * @property {object} engine - * @property {boolean} engine.oxide - * @property {boolean} engine.stable - */ - -/** - * @param {(data: CrossCheck) => void} fn - */ -export function crosscheck(fn) { - let engines = [{ engine: 'Stable' }] - - describe.each(engines)('$engine', ({ engine }) => { - let engines = { - oxide: nullProxy, - stable: globalThis, - engine: { oxide: engine === 'Oxide', stable: engine === 'Stable' }, - } - - beforeEach(() => { - globalThis.__OXIDE__ = engines.engine.oxide - }) - - fn(engines) - }) -} diff --git a/tests/variants.test.css b/tests/variants.test.css index 0b27b228141c..9fd06868669b 100644 --- a/tests/variants.test.css +++ b/tests/variants.test.css @@ -1,58 +1,6 @@ *, :before, -:after { - --tw-border-spacing-x: 0; - --tw-border-spacing-y: 0; - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - --tw-pan-x: ; - --tw-pan-y: ; - --tw-pinch-zoom: ; - --tw-scroll-snap-strictness: proximity; - --tw-gradient-from-position: ; - --tw-gradient-via-position: ; - --tw-gradient-to-position: ; - --tw-ordinal: ; - --tw-slashed-zero: ; - --tw-numeric-figure: ; - --tw-numeric-spacing: ; - --tw-numeric-fraction: ; - --tw-ring-inset: ; - --tw-ring-offset-width: 0px; - --tw-ring-offset-color: #fff; - --tw-ring-color: #3b82f680; - --tw-ring-offset-shadow: 0 0 #0000; - --tw-ring-shadow: 0 0 #0000; - --tw-shadow: 0 0 #0000; - --tw-shadow-colored: 0 0 #0000; - --tw-blur: ; - --tw-brightness: ; - --tw-contrast: ; - --tw-grayscale: ; - --tw-hue-rotate: ; - --tw-invert: ; - --tw-saturate: ; - --tw-sepia: ; - --tw-drop-shadow: ; - --tw-backdrop-blur: ; - --tw-backdrop-brightness: ; - --tw-backdrop-contrast: ; - --tw-backdrop-grayscale: ; - --tw-backdrop-hue-rotate: ; - --tw-backdrop-invert: ; - --tw-backdrop-opacity: ; - --tw-backdrop-saturate: ; - --tw-backdrop-sepia: ; - --tw-contain-size: ; - --tw-contain-layout: ; - --tw-contain-paint: ; - --tw-contain-style: ; -} +:after, ::backdrop { --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; @@ -192,7 +140,7 @@ .first\:shadow-md:first-child, .last\:shadow-md:last-child, .only\:shadow-md:only-child, -.odd\:shadow-md:nth-child(2n + 1), +.odd\:shadow-md:nth-child(odd), .even\:shadow-md:nth-child(2n), .first-of-type\:shadow-md:first-of-type, .last-of-type\:shadow-md:last-of-type, @@ -211,8 +159,24 @@ .default\:shadow-md:default, .checked\:shadow-md:checked, .indeterminate\:shadow-md:indeterminate, -.placeholder-shown\:shadow-md:placeholder-shown, -.autofill\:shadow-md:autofill, +.placeholder-shown\:shadow-md:placeholder-shown { + --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); +} +.autofill\:shadow-md:-webkit-autofill { + --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); +} +.autofill\:shadow-md:autofill { + --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); +} .optional\:shadow-md:optional, .required\:shadow-md:required, .valid\:shadow-md:valid, @@ -258,7 +222,7 @@ .group:first-child .group-first\:shadow-md, .group:last-child .group-last\:shadow-md, .group:only-child .group-only\:shadow-md, -.group:nth-child(2n + 1) .group-odd\:shadow-md, +.group:nth-child(odd) .group-odd\:shadow-md, .group:nth-child(2n) .group-even\:shadow-md, .group:first-of-type .group-first-of-type\:shadow-md, .group:last-of-type .group-last-of-type\:shadow-md, @@ -277,8 +241,24 @@ .group:default .group-default\:shadow-md, .group:checked .group-checked\:shadow-md, .group:indeterminate .group-indeterminate\:shadow-md, -.group:placeholder-shown .group-placeholder-shown\:shadow-md, -.group:autofill .group-autofill\:shadow-md, +.group:placeholder-shown .group-placeholder-shown\:shadow-md { + --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); +} +.group:-webkit-autofill .group-autofill\:shadow-md { + --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); +} +.group:autofill .group-autofill\:shadow-md { + --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); +} .group:optional .group-optional\:shadow-md, .group:required .group-required\:shadow-md, .group:valid .group-valid\:shadow-md, @@ -319,7 +299,7 @@ .peer:first-child ~ .peer-first\:shadow-md, .peer:last-child ~ .peer-last\:shadow-md, .peer:only-child ~ .peer-only\:shadow-md, -.peer:nth-child(2n + 1) ~ .peer-odd\:shadow-md, +.peer:nth-child(odd) ~ .peer-odd\:shadow-md, .peer:nth-child(2n) ~ .peer-even\:shadow-md, .peer:first-of-type ~ .peer-first-of-type\:shadow-md, .peer:last-of-type ~ .peer-last-of-type\:shadow-md, @@ -338,8 +318,24 @@ .peer:default ~ .peer-default\:shadow-md, .peer:checked ~ .peer-checked\:shadow-md, .peer:indeterminate ~ .peer-indeterminate\:shadow-md, -.peer:placeholder-shown ~ .peer-placeholder-shown\:shadow-md, -.peer:autofill ~ .peer-autofill\:shadow-md, +.peer:placeholder-shown ~ .peer-placeholder-shown\:shadow-md { + --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); +} +.peer:-webkit-autofill ~ .peer-autofill\:shadow-md { + --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); +} +.peer:autofill ~ .peer-autofill\:shadow-md { + --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), + var(--tw-shadow); +} .peer:optional ~ .peer-optional\:shadow-md, .peer:required ~ .peer-required\:shadow-md, .peer:valid ~ .peer-valid\:shadow-md, @@ -393,7 +389,7 @@ background-color: rgb(253 224 71 / var(--tw-bg-opacity)); } } -@media (min-width: 640px) { +@media (width >= 640px) { .sm\:shadow-md, .sm\:active\:shadow-md:active { --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; @@ -403,7 +399,7 @@ var(--tw-shadow); } } -@media (min-width: 768px) { +@media (width >= 768px) { .md\:shadow-md, .group:focus .md\:group-focus\:shadow-md { --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; @@ -413,7 +409,7 @@ var(--tw-shadow); } } -@media (min-width: 1024px) { +@media (width >= 1024px) { @keyframes spin { to { transform: rotate(360deg); @@ -430,7 +426,7 @@ var(--tw-shadow); } } -@media (min-width: 1280px) { +@media (width >= 1280px) { .xl\:shadow-md { --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), @@ -439,7 +435,7 @@ var(--tw-shadow); } } -@media (min-width: 1536px) { +@media (width >= 1536px) { .\32 xl\:shadow-md { --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), @@ -460,37 +456,19 @@ background-color: rgb(253 224 71 / var(--tw-bg-opacity)); } } -.ltr\:shadow-md:where([dir="ltr"], [dir="ltr"] *) { - --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; - --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), - var(--tw-shadow); -} -.rtl\:shadow-md:where([dir="rtl"], [dir="rtl"] *) { - --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; - --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), - var(--tw-shadow); -} -.dark\:shadow-md:where(.dark, .dark *) { - --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; - --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), - var(--tw-shadow); -} -.group:disabled:focus:hover .dark\:group-disabled\:group-focus\:group-hover\:shadow-md:where(.dark, .dark *) { - --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; - --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), - var(--tw-shadow); -} -.peer:disabled:focus:hover ~ .dark\:peer-disabled\:peer-focus\:peer-hover\:shadow-md:where(.dark, .dark *) { +.ltr\:shadow-md:where([dir="ltr"], [dir="ltr"] *), +.rtl\:shadow-md:where([dir="rtl"], [dir="rtl"] *), +.dark\:shadow-md:where(.dark, .dark *), +.group:disabled:focus:hover + .dark\:group-disabled\:group-focus\:group-hover\:shadow-md:where(.dark, .dark *), +.peer:disabled:focus:hover + ~ .dark\:peer-disabled\:peer-focus\:peer-hover\:shadow-md:where(.dark, .dark *) { --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } -@media (min-width: 1024px) { +@media (width >= 1024px) { .lg\:dark\:shadow-md:where(.dark, .dark *) { --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), @@ -499,7 +477,7 @@ var(--tw-shadow); } } -@media (min-width: 1280px) { +@media (width >= 1280px) { .xl\:dark\:disabled\:shadow-md:disabled:where(.dark, .dark *) { --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), @@ -508,7 +486,7 @@ var(--tw-shadow); } } -@media (min-width: 1536px) { +@media (width >= 1536px) { @media (prefers-reduced-motion: no-preference) { .\32 xl\:dark\:motion-safe\:focus-within\:shadow-md:focus-within:where(.dark, .dark *) { --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a; @@ -530,3 +508,5 @@ background-color: rgb(253 224 71 / var(--tw-bg-opacity)); } } + + diff --git a/tests/variants.test.js b/tests/variants.test.js index c22116691e4e..8bc5084c24e1 100644 --- a/tests/variants.test.js +++ b/tests/variants.test.js @@ -1,399 +1,77 @@ import fs from 'fs' import path from 'path' import postcss from 'postcss' -import { crosscheck, run, html, css, defaults } from './util/run' - -crosscheck(({ stable, oxide }) => { - test('variants', () => { - let config = { - darkMode: 'selector', - content: [path.resolve(__dirname, './variants.test.html')], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind base; - @tailwind components; - @tailwind utilities; - ` - - return run(input, config).then((result) => { - stable - .expect(result.css) - .toMatchFormattedCss( - fs.readFileSync(path.resolve(__dirname, './variants.test.css'), 'utf8') - ) - }) - }) - - test('order matters and produces different behaviour', () => { - let config = { - content: [ - { - raw: html` -
-
- `, - }, - ], - } - - return run('@tailwind utilities', config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - .hover\:file\:\[--value\:1\]::-webkit-file-upload-button:hover { - --value: 1; - } - .hover\:file\:\[--value\:1\]::file-selector-button:hover { - --value: 1; - } - .file\:hover\:\[--value\:2\]:hover::-webkit-file-upload-button { - --value: 2; - } - .file\:hover\:\[--value\:2\]:hover::file-selector-button { - --value: 2; - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - .hover\:file\:\[--value\:1\]::-webkit-file-upload-button:hover { - --value: 1; - } - .hover\:file\:\[--value\:1\]::file-selector-button:hover { - --value: 1; - } - .file\:hover\:\[--value\:2\]:hover::-webkit-file-upload-button { - --value: 2; - } - .file\:hover\:\[--value\:2\]:hover::file-selector-button { - --value: 2; - } - `) - }) - }) - - describe('custom advanced variants', () => { - test('at-rules without params', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - plugins: [ - function ({ addVariant }) { - addVariant('ogre', '@layer') - }, - ], - } - - return run('@tailwind components; @tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - @layer { - .ogre\:text-center { - text-align: center; - } - } - `) - }) - }) - - test('prose-headings usage on its own', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - plugins: [ - function ({ addVariant }) { - addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)') - }, - ], - } - - return run('@tailwind components;@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - :where(.prose-headings\:text-center) :is(h1, h2, h3, h4) { - text-align: center; - } - `) - }) - }) - - test('prose-headings with another "simple" variant', () => { - let config = { - content: [ - { - raw: html` -
-
- `, - }, - ], - plugins: [ - function ({ addVariant }) { - addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)') - }, - ], - } - - return run('@tailwind components;@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - :where(.hover\:prose-headings\:text-center) :is(h1, h2, h3, h4):hover, - :where(.prose-headings\:hover\:text-center:hover) :is(h1, h2, h3, h4) { - text-align: center; - } - `) - }) - }) - - test('prose-headings with another "complex" variant', () => { - let config = { - content: [ - { - raw: html` -
-
- `, - }, - ], - plugins: [ - function ({ addVariant }) { - addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)') - }, - ], - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .group:hover :where(.group-hover\:prose-headings\:text-center) :is(h1, h2, h3, h4), - :where(.group:hover .prose-headings\:group-hover\:text-center) :is(h1, h2, h3, h4) { - text-align: center; - } - `) - }) - }) - - test('using variants with multi-class selectors', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - plugins: [ - function ({ addVariant, addComponents }) { - addComponents({ - '.parent .child': { - foo: 'bar', - }, - }) - addVariant('screen', '@media screen') - }, - ], - } - - return run('@tailwind components;@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - @media screen { - .screen\:parent .child, - .parent .screen\:child { - foo: bar; - } - } - `) - }) - }) - - test('using multiple classNames in your custom variant', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - plugins: [ - function ({ addVariant }) { - addVariant('my-variant', '&:where(.one, .two, .three)') - }, - ], - } - - let input = css` - @tailwind components; - @tailwind utilities; - - @layer components { - .test { - @apply my-variant:italic; - } - } - ` - - return run(input, config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .test:where(.one, .two, .three) { - font-style: italic; - } - .my-variant\:underline:where(.one, .two, .three) { - text-decoration-line: underline; - } - `) - }) - }) - - test('variant format string must include at-rule or & (1)', async () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - plugins: [ - function ({ addVariant }) { - addVariant('wtf-bbq', 'lol') - }, - ], - } - - await expect(run('@tailwind components;@tailwind utilities', config)).rejects.toThrowError( - "Your custom variant `wtf-bbq` has an invalid format string. Make sure it's an at-rule or contains a `&` placeholder." - ) - }) - - test('variant format string must include at-rule or & (2)', async () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - plugins: [ - function ({ addVariant }) { - addVariant('wtf-bbq', () => 'lol') - }, - ], - } - - await expect(run('@tailwind components;@tailwind utilities', config)).rejects.toThrowError( - "Your custom variant `wtf-bbq` has an invalid format string. Make sure it's an at-rule or contains a `&` placeholder." - ) - }) - - it('should allow modifiers with dashes', () => { - let config = { - content: [ - { - raw: html`
`, - }, - ], - plugins: [], - } - - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - .group\/test-modifier:has([data-test='test']) - .group-has-\[\[data-test\=test\]\]\/test-modifier\:block { - display: block; - } - `) - }) - }) - }) - - test('stacked peer variants', async () => { - let config = { - content: [{ raw: 'peer-disabled:peer-focus:peer-hover:border-blue-500' }], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind base; - @tailwind components; - @tailwind utilities; - ` - - let result = await run(input, config) - stable.expect(result.css).toIncludeCss(css` - .peer:disabled:focus:hover ~ .peer-disabled\:peer-focus\:peer-hover\:border-blue-500 { - --tw-border-opacity: 1; - border-color: rgb(59 130 246 / var(--tw-border-opacity)); - } - `) - oxide.expect(result.css).toIncludeCss(css` - .peer:disabled:focus:hover ~ .peer-disabled\:peer-focus\:peer-hover\:border-blue-500 { - border-color: #3b82f6; - } - `) +import { run, html, css, defaults } from './util/run' + +test('variants', () => { + let config = { + darkMode: 'selector', + content: [path.resolve(__dirname, './variants.test.html')], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss( + fs.readFileSync(path.resolve(__dirname, './variants.test.css'), 'utf8') + ) }) +}) - it('should properly handle keyframes with multiple variants', async () => { - let config = { - content: [ - { - raw: 'animate-spin hover:animate-spin focus:animate-spin hover:animate-bounce focus:animate-bounce', - }, - ], - } - - let input = css` - @tailwind components; - @tailwind utilities; - ` +test('order matters and produces different behaviour', () => { + let config = { + content: [ + { + raw: html` +
+
+ `, + }, + ], + } - let result = await run(input, config) + 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; - } - @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; - } - } - .hover\:animate-bounce:hover { - animation: 1s infinite bounce; + .hover\:file\:\[--value\:1\]::-webkit-file-upload-button:hover { + --value: 1; } - .hover\:animate-spin:hover { - animation: 1s linear infinite spin; + .hover\:file\:\[--value\:1\]::file-selector-button:hover { + --value: 1; } - .focus\:animate-bounce:focus { - animation: 1s infinite bounce; + .file\:hover\:\[--value\:2\]:hover::-webkit-file-upload-button { + --value: 2; } - .focus\:animate-spin:focus { - animation: 1s linear infinite spin; + .file\:hover\:\[--value\:2\]:hover::file-selector-button { + --value: 2; } `) }) +}) - test('custom addVariant with more complex media query params', () => { +describe('custom advanced variants', () => { + test('at-rules without params', () => { let config = { content: [ { - raw: html`
`, + raw: html`
`, }, ], plugins: [ function ({ addVariant }) { - addVariant('magic', '@media screen and (max-width: 600px)') + addVariant('ogre', '@layer') }, ], } - return run('@tailwind components;@tailwind utilities', config).then((result) => { + return run('@tailwind components; @tailwind utilities', config).then((result) => { return expect(result.css).toMatchFormattedCss(css` - @media screen and (max-width: 600px) { - .magic\:text-center { + @layer { + .ogre\:text-center { text-align: center; } } @@ -401,64 +79,63 @@ crosscheck(({ stable, oxide }) => { }) }) - test('custom addVariant with nested media & format shorthand', () => { + test('prose-headings usage on its own', () => { let config = { content: [ { - raw: html`
`, + raw: html`
`, }, ], plugins: [ function ({ addVariant }) { - addVariant('magic', '@supports (hover: hover) { @media print { &:disabled } }') + addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)') }, ], } return run('@tailwind components;@tailwind utilities', config).then((result) => { return expect(result.css).toMatchFormattedCss(css` - @supports (hover: hover) { - @media print { - .magic\:text-center:disabled { - text-align: center; - } - } + :where(.prose-headings\:text-center) :is(h1, h2, h3, h4) { + text-align: center; } `) }) }) - test('before and after variants are a bit special, and forced to the end', () => { + test('prose-headings with another "simple" variant', () => { let config = { content: [ { raw: html` -
-
+
+
`, }, ], - plugins: [], + plugins: [ + function ({ addVariant }) { + addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)') + }, + ], } return run('@tailwind components;@tailwind utilities', config).then((result) => { return expect(result.css).toMatchFormattedCss(css` - .before\:hover\:text-center:hover:before, - .hover\:before\:text-center:hover:before { - content: var(--tw-content); + :where(.hover\:prose-headings\:text-center) :is(h1, h2, h3, h4):hover, + :where(.prose-headings\:hover\:text-center:hover) :is(h1, h2, h3, h4) { text-align: center; } `) }) }) - test('before and after variants are a bit special, and forced to the end (2)', () => { + test('prose-headings with another "complex" variant', () => { let config = { content: [ { raw: html` -
-
+
+
`, }, ], @@ -469,192 +146,127 @@ crosscheck(({ stable, oxide }) => { ], } - return run('@tailwind components;@tailwind utilities', config).then((result) => { + return run('@tailwind utilities', config).then((result) => { return expect(result.css).toMatchFormattedCss(css` - :where(.before\:prose-headings\:text-center) :is(h1, h2, h3, h4):before, - :where(.prose-headings\:before\:text-center) :is(h1, h2, h3, h4):before { - content: var(--tw-content); + .group:hover :where(.group-hover\:prose-headings\:text-center) :is(h1, h2, h3, h4), + :where(.group:hover .prose-headings\:group-hover\:text-center) :is(h1, h2, h3, h4) { text-align: center; } `) }) }) - test('returning non-strings and non-selectors in addVariant', () => { - /** @type {import('../types/config').Config} */ + test('using variants with multi-class selectors', () => { let config = { content: [ { - raw: html` -
-
- `, + raw: html`
`, }, ], plugins: [ - function ({ addVariant, e }) { - addVariant('peer-aria-expanded', ({ modifySelectors, separator }) => - // Returning anything other string | string[] | undefined here is not supported - // But we're trying to be lenient here and just throw it out - modifySelectors( - ({ className }) => - `.peer[aria-expanded="true"] ~ .${e(`peer-aria-expanded${separator}${className}`)}` - ) - ) - - addVariant('peer-aria-expanded-2', ({ modifySelectors, separator }) => { - let nodes = modifySelectors( - ({ className }) => `.${e(`peer-aria-expanded-2${separator}${className}`)}` - ) - - return [ - // Returning anything other than strings here is not supported - // But we're trying to be lenient here and just throw it out - nodes, - '.peer[aria-expanded="false"] ~ &', - ] + function ({ addVariant, addComponents }) { + addComponents({ + '.parent .child': { + foo: 'bar', + }, }) + addVariant('screen', '@media screen') }, ], } return run('@tailwind components;@tailwind utilities', config).then((result) => { return expect(result.css).toMatchFormattedCss(css` - .peer[aria-expanded='true'] ~ .peer-aria-expanded\:text-center, - .peer[aria-expanded='false'] ~ .peer-aria-expanded-2\:text-center { - text-align: center; + @media screen { + .screen\:parent .child, + .parent .screen\:child { + foo: bar; + } } `) }) }) - it('should not generate variants of user css if it is not inside a layer', () => { + test('using multiple classNames in your custom variant', () => { let config = { - content: [{ raw: html`
` }], - plugins: [], + content: [ + { + raw: html`
`, + }, + ], + plugins: [ + function ({ addVariant }) { + addVariant('my-variant', '&:where(.one, .two, .three)') + }, + ], } let input = css` @tailwind components; @tailwind utilities; - .foo { - color: red; + @layer components { + .test { + @apply my-variant:italic; + } } ` return run(input, config).then((result) => { return expect(result.css).toMatchFormattedCss(css` - .foo { - color: red; + .test:where(.one, .two, .three) { + font-style: italic; } - `) - }) - }) - - it('should be possible to use responsive modifiers that are defined with special characters', () => { - let config = { - content: [{ raw: html`
` }], - theme: { - screens: { - ' { - return expect(result.css).toMatchFormattedCss(css` - @media (max-width: 399px) { - .\ { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } - - return run('@tailwind base', config).then((result) => { - return expect(result.css).toMatchFormattedCss( - css` - ${defaults} - ` - ) - }) - }) - - it('variants for components should not be produced in a file without a components layer', () => { + test('variant format string must include at-rule or & (1)', async () => { let config = { - content: [{ raw: html`
` }], + content: [ + { + raw: html`
`, + }, + ], + plugins: [ + function ({ addVariant }) { + addVariant('wtf-bbq', 'lol') + }, + ], } - return run('@tailwind utilities', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - @media (min-width: 640px) { - .sm\:underline { - text-decoration-line: underline; - } - } - `) - }) + await expect(run('@tailwind components;@tailwind utilities', config)).rejects.toThrowError( + "Your custom variant `wtf-bbq` has an invalid format string. Make sure it's an at-rule or contains a `&` placeholder." + ) }) - it('variants for utilities should not be produced in a file without a utilities layer', () => { + test('variant format string must include at-rule or & (2)', async () => { let config = { - content: [{ raw: html`
` }], + content: [ + { + raw: html`
`, + }, + ], + plugins: [ + function ({ addVariant }) { + addVariant('wtf-bbq', () => 'lol') + }, + ], } - return run('@tailwind components', config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - @media (min-width: 640px) { - .sm\:container { - width: 100%; - } - @media (min-width: 640px) { - .sm\:container { - max-width: 640px; - } - } - @media (min-width: 768px) { - .sm\:container { - max-width: 768px; - } - } - @media (min-width: 1024px) { - .sm\:container { - max-width: 1024px; - } - } - @media (min-width: 1280px) { - .sm\:container { - max-width: 1280px; - } - } - @media (min-width: 1536px) { - .sm\:container { - max-width: 1536px; - } - } - } - `) - }) + await expect(run('@tailwind components;@tailwind utilities', config)).rejects.toThrowError( + "Your custom variant `wtf-bbq` has an invalid format string. Make sure it's an at-rule or contains a `&` placeholder." + ) }) - test('The visited variant removes opacity support', () => { + it('should allow modifiers with dashes', () => { let config = { content: [ { - raw: html` - Look, it's a link! - `, + raw: html`
`, }, ], plugins: [], @@ -662,604 +274,955 @@ crosscheck(({ stable, oxide }) => { return run('@tailwind utilities', config).then((result) => { return expect(result.css).toMatchFormattedCss(css` - .visited\:border-red-500:visited { - border-color: #ef4444; - } - .visited\:bg-red-500:visited { - background-color: #ef4444; - } - .visited\:text-red-500:visited { - color: #ef4444; + .group\/test-modifier:has([data-test='test']) + .group-has-\[\[data-test\=test\]\]\/test-modifier\:block { + display: block; } `) }) }) +}) - it('appends variants to the correct place when using postcss documents', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], - corePlugins: { preflight: false }, +test('stacked peer variants', async () => { + let config = { + content: [{ raw: 'peer-disabled:peer-focus:peer-hover:border-blue-500' }], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` + + let result = await run(input, config) + expect(result.css).toIncludeCss(css` + .peer:disabled:focus:hover ~ .peer-disabled\:peer-focus\:peer-hover\:border-blue-500 { + --tw-border-opacity: 1; + border-color: rgb(59 130 246 / var(--tw-border-opacity)); } + `) +}) - const doc = postcss.document() - doc.append(postcss.parse(`a {}`)) - doc.append(postcss.parse(`@tailwind base`)) - doc.append(postcss.parse(`@tailwind utilities`)) - doc.append(postcss.parse(`b {}`)) +it('should properly handle keyframes with multiple variants', async () => { + let config = { + content: [ + { + raw: 'animate-spin hover:animate-spin focus:animate-spin hover:animate-bounce focus:animate-bounce', + }, + ], + } + + let input = css` + @tailwind components; + @tailwind utilities; + ` + + let result = await run(input, config) + expect(result.css).toMatchFormattedCss(css` + @keyframes spin { + to { + transform: rotate(360deg); + } + } + .animate-spin { + animation: 1s linear infinite spin; + } + @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; + } + } + .hover\:animate-bounce:hover { + animation: 1s infinite bounce; + } + .hover\:animate-spin:hover { + animation: 1s linear infinite spin; + } + .focus\:animate-bounce:focus { + animation: 1s infinite bounce; + } + .focus\:animate-spin:focus { + animation: 1s linear infinite spin; + } + `) +}) - const result = doc.toResult() +test('custom addVariant with more complex media query params', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + plugins: [ + function ({ addVariant }) { + addVariant('magic', '@media screen and (max-width: 600px)') + }, + ], + } - return run(result, config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - ${defaults} - .underline { - text-decoration-line: underline; + return run('@tailwind components;@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + @media screen and (max-width: 600px) { + .magic\:text-center { + text-align: center; } - @media (min-width: 640px) { - .sm\:underline { - text-decoration-line: underline; - } - } - `) - }) + } + `) }) +}) - it('variants support multiple, grouped selectors (html)', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind utilities; - @layer utilities { - .base1 .foo, - .base1 .bar { - color: red; +test('custom addVariant with nested media & format shorthand', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + plugins: [ + function ({ addVariant }) { + addVariant('magic', '@supports (hover: hover) { @media print { &:disabled } }') + }, + ], + } + + return run('@tailwind components;@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + @supports (hover: hover) { + @media print { + .magic\:text-center:disabled { + text-align: center; + } } + } + `) + }) +}) - .base2 .bar .base2-foo { - color: red; - } +test('before and after variants are a bit special, and forced to the end', () => { + let config = { + content: [ + { + raw: html` +
+
+ `, + }, + ], + plugins: [], + } + + return run('@tailwind components;@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .before\:hover\:text-center:hover:before, + .hover\:before\:text-center:hover:before { + content: var(--tw-content); + text-align: center; } - ` + `) + }) +}) - return run(input, config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - @media (min-width: 640px) { - .sm\:base1 .foo, - .sm\:base1 .bar, - .sm\:base2 .bar .base2-foo { - color: red; - } - } - `) - }) +test('before and after variants are a bit special, and forced to the end (2)', () => { + let config = { + content: [ + { + raw: html` +
+
+ `, + }, + ], + plugins: [ + function ({ addVariant }) { + addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)') + }, + ], + } + + return run('@tailwind components;@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + :where(.before\:prose-headings\:text-center) :is(h1, h2, h3, h4):before, + :where(.prose-headings\:before\:text-center) :is(h1, h2, h3, h4):before { + content: var(--tw-content); + text-align: center; + } + `) }) +}) - it('variants support multiple, grouped selectors (apply)', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], - corePlugins: { preflight: false }, - } +test('returning non-strings and non-selectors in addVariant', () => { + /** @type {import('../types/config').Config} */ + let config = { + content: [ + { + raw: html` +
+
+ `, + }, + ], + plugins: [ + function ({ addVariant, e }) { + addVariant('peer-aria-expanded', ({ modifySelectors, separator }) => + // Returning anything other string | string[] | undefined here is not supported + // But we're trying to be lenient here and just throw it out + modifySelectors( + ({ className }) => + `.peer[aria-expanded="true"] ~ .${e(`peer-aria-expanded${separator}${className}`)}` + ) + ) - let input = css` - @tailwind utilities; - @layer utilities { - .base .foo, - .base .bar { - color: red; - } - } - .baz { - @apply sm:base; - } - ` + addVariant('peer-aria-expanded-2', ({ modifySelectors, separator }) => { + let nodes = modifySelectors( + ({ className }) => `.${e(`peer-aria-expanded-2${separator}${className}`)}` + ) - return run(input, config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - @media (min-width: 640px) { - .baz .foo, - .baz .bar { - color: red; - } - } - `) - }) + return [ + // Returning anything other than strings here is not supported + // But we're trying to be lenient here and just throw it out + nodes, + '.peer[aria-expanded="false"] ~ &', + ] + }) + }, + ], + } + + return run('@tailwind components;@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .peer[aria-expanded='true'] ~ .peer-aria-expanded\:text-center, + .peer[aria-expanded='false'] ~ .peer-aria-expanded-2\:text-center { + text-align: center; + } + `) }) +}) - it('variants only picks the used selectors in a group (html)', () => { - let config = { - content: [{ raw: html`
` }], - plugins: [], - corePlugins: { preflight: false }, +it('should not generate variants of user css if it is not inside a layer', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + } + + let input = css` + @tailwind components; + @tailwind utilities; + + .foo { + color: red; } + ` - let input = css` - @tailwind utilities; - @layer utilities { - .a, - .b { - color: red; - } + return run(input, config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .foo { + color: red; } - ` + `) + }) +}) - return run(input, config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` - @media (min-width: 640px) { - .sm\:b { - color: red; - } +it('should be possible to use responsive modifiers that are defined with special characters', () => { + let config = { + content: [{ raw: html`
` }], + theme: { + screens: { + ' { + return expect(result.css).toMatchFormattedCss(css` + @media (max-width: 399px) { + .\ { - let config = { - content: [{ raw: html`
` }], - plugins: [], - corePlugins: { preflight: false }, - } +it('including just the base layer should not produce variants', () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } - let input = css` - @tailwind utilities; - @layer utilities { - .a, - .b { - color: red; + return run('@tailwind base', config).then((result) => { + return expect(result.css).toMatchFormattedCss( + css` + ${defaults} + ` + ) + }) +}) + +it('variants for components should not be produced in a file without a components layer', () => { + let config = { + content: [{ raw: html`
` }], + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + @media (min-width: 640px) { + .sm\:underline { + text-decoration-line: underline; } } - .baz { - @apply sm:b; - } - ` + `) + }) +}) - return run(input, config).then((result) => { - return expect(result.css).toMatchFormattedCss(css` +it('variants for utilities should not be produced in a file without a utilities layer', () => { + let config = { + content: [{ raw: html`
` }], + } + + return run('@tailwind components', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + @media (min-width: 640px) { + .sm\:container { + width: 100%; + } @media (min-width: 640px) { - .baz { - color: red; + .sm\:container { + max-width: 640px; } } - `) - }) + @media (min-width: 768px) { + .sm\:container { + max-width: 768px; + } + } + @media (min-width: 1024px) { + .sm\:container { + max-width: 1024px; + } + } + @media (min-width: 1280px) { + .sm\:container { + max-width: 1280px; + } + } + @media (min-width: 1536px) { + .sm\:container { + max-width: 1536px; + } + } + } + `) }) +}) - test('hoverOnlyWhenSupported adds hover and pointer media features by default', () => { - let config = { - future: { - hoverOnlyWhenSupported: true, +test('The visited variant removes opacity support', () => { + let config = { + content: [ + { + raw: html` + Look, it's a link! + `, }, - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind base; - @tailwind components; - @tailwind utilities; - ` + ], + plugins: [], + } + + return run('@tailwind utilities', config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + .visited\:border-red-500:visited { + border-color: #ef4444; + } + .visited\:bg-red-500:visited { + background-color: #ef4444; + } + .visited\:text-red-500:visited { + color: #ef4444; + } + `) + }) +}) - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - ${defaults} - @media (hover: hover) and (pointer: fine) { - .hover\:underline:hover, - .group:hover .group-hover\:underline, - .peer:hover ~ .peer-hover\:underline { - text-decoration-line: underline; - } +it('appends variants to the correct place when using postcss documents', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + corePlugins: { preflight: false }, + } + + const doc = postcss.document() + doc.append(postcss.parse(`a {}`)) + doc.append(postcss.parse(`@tailwind base`)) + doc.append(postcss.parse(`@tailwind utilities`)) + doc.append(postcss.parse(`b {}`)) + + const result = doc.toResult() + + return run(result, config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + ${defaults} + .underline { + text-decoration-line: underline; + } + @media (min-width: 640px) { + .sm\:underline { + text-decoration-line: underline; } - `) - }) + } + `) }) +}) - test('multi-class utilities handle selector-mutating variants correctly', () => { - let config = { - content: [ - { - raw: html`
`, - }, - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - } +it('variants support multiple, grouped selectors (html)', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + @layer utilities { + .base1 .foo, + .base1 .bar { + color: red; + } - let input = css` - @tailwind utilities; - @layer utilities { - .foo.bar.baz { - color: red; - } - .foo1 .bar1 .baz1 { + .base2 .bar .base2-foo { + color: red; + } + } + ` + + return run(input, config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + @media (min-width: 640px) { + .sm\:base1 .foo, + .sm\:base1 .bar, + .sm\:base2 .bar .base2-foo { color: red; } } - ` + `) + }) +}) - // The second set of ::after cases (w/ descendant selectors) - // are clearly "wrong" BUT you can't have a descendant of a - // pseudo - element so the utilities `after:foo1` and - // `after:bar1` are non-sensical so this is still - // perfectly fine behavior +it('variants support multiple, grouped selectors (apply)', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + @layer utilities { + .base .foo, + .base .bar { + color: red; + } + } + .baz { + @apply sm:base; + } + ` - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - .after\:foo.bar.baz:after, - .after\:bar.foo.baz:after, - .after\:baz.foo.bar:after, - .after\:foo1 .bar1 .baz1:after, - .foo1 .after\:bar1 .baz1:after, - .foo1 .bar1 .after\:baz1:after { - content: var(--tw-content); + return run(input, config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + @media (min-width: 640px) { + .baz .foo, + .baz .bar { color: red; } - .hover\:foo:hover.bar.baz, - .hover\:bar:hover.foo.baz, - .hover\:baz:hover.foo.bar, - .hover\:foo1:hover .bar1 .baz1, - .foo1 .hover\:bar1:hover .baz1, - .foo1 .bar1 .hover\:baz1:hover, - .group:hover .group-hover\:foo.bar.baz, - .group:hover .group-hover\:bar.foo.baz, - .group:hover .group-hover\:baz.foo.bar, - .group:hover .group-hover\:foo1 .bar1 .baz1, - .foo1 .group:hover .group-hover\:bar1 .baz1, - .foo1 .bar1 .group:hover .group-hover\:baz1, - .peer:checked ~ .peer-checked\:foo.bar.baz, - .peer:checked ~ .peer-checked\:bar.foo.baz, - .peer:checked ~ .peer-checked\:baz.foo.bar, - .peer:checked ~ .peer-checked\:foo1 .bar1 .baz1, - .foo1 .peer:checked ~ .peer-checked\:bar1 .baz1, - .foo1 .bar1 .peer:checked ~ .peer-checked\:baz1 { - color: red; - } - `) - }) + } + `) }) +}) - test('class inside pseudo-class function :has', () => { - let config = { - content: [ - { raw: html`
` }, - { raw: html`
` }, - { raw: html`
` }, - ], - corePlugins: { preflight: false }, +it('variants only picks the used selectors in a group (html)', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + @layer utilities { + .a, + .b { + color: red; + } } + ` - let input = css` - @tailwind utilities; - @layer utilities { - :where(.foo) { + return run(input, config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + @media (min-width: 640px) { + .sm\:b { color: red; } - :is(.foo, .bar, .baz) { - color: orange; - } - :is(.foo) { - color: yellow; - } - html:has(.foo) { - color: green; - } } - ` - - return run(input, config).then((result) => { - expect(result.css).toMatchFormattedCss(css` - :where(.foo) { - color: red; - } - :is(.foo, .bar, .baz) { - color: orange; - } - .foo { - color: #ff0; - } - html:has(.foo) { - color: green; - } - :where(.hover\:foo:hover) { - color: red; - } - :is(.hover\:foo:hover, .bar, .baz), - :is(.foo, .hover\:bar:hover, .baz), - :is(.foo, .bar, .hover\:baz:hover) { - color: orange; - } - .hover\:foo:hover { - color: #ff0; - } - html:has(.hover\:foo:hover) { - color: green; - } - @media (min-width: 640px) { - :where(.sm\:foo) { - color: red; - } - :is(.sm\:foo, .bar, .baz), - :is(.foo, .sm\:bar, .baz), - :is(.foo, .bar, .sm\:baz) { - color: orange; - } - .sm\:foo { - color: #ff0; - } - html:has(.sm\:foo) { - color: green; - } - } - `) - }) + `) }) +}) - test('variant functions returning arrays should output correct results when nesting', async () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - plugins: [ - function ({ addUtilities, addVariant }) { - addVariant('test', () => ['@media (test)']) - addUtilities({ - '.foo': { - display: 'grid', - '> *': { - 'grid-column': 'span 2', - }, - }, - }) - }, - ], +it('variants only picks the used selectors in a group (apply)', () => { + let config = { + content: [{ raw: html`
` }], + plugins: [], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + @layer utilities { + .a, + .b { + color: red; + } } + .baz { + @apply sm:b; + } + ` - let input = css` - @tailwind utilities; - ` - - let result = await run(input, config) - - expect(result.css).toMatchFormattedCss(css` - @media (test) { - .test\:foo { - display: grid; - } - .test\:foo > * { - grid-column: span 2; + return run(input, config).then((result) => { + return expect(result.css).toMatchFormattedCss(css` + @media (min-width: 640px) { + .baz { + color: red; } } `) }) +}) - test('variants with slashes in them work', () => { - let config = { - content: [ - { - raw: html`
ar-1/10
`, - }, - ], - theme: { - extend: { - screens: { - 'ar-1/10': { raw: '(min-aspect-ratio: 1/10)' }, - }, - }, +test('hoverOnlyWhenSupported adds hover and pointer media features by default', () => { + let config = { + future: { + hoverOnlyWhenSupported: true, + }, + content: [ + { + raw: html`
`, }, - corePlugins: { preflight: false }, - } + ], + corePlugins: { preflight: false }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind base; + @tailwind components; + @tailwind utilities; + ` - return run(input, config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - @media (min-aspect-ratio: 1 / 10) { - .ar-1\/10\:text-red-500 { - --tw-text-opacity: 1; - color: rgb(239 68 68 / var(--tw-text-opacity)); - } - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - @media (min-aspect-ratio: 1 / 10) { - .ar-1\/10\:text-red-500 { - color: #ef4444; - } + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + ${defaults} + @media (hover: hover) and (pointer: fine) { + .hover\:underline:hover, + .group:hover .group-hover\:underline, + .peer:hover ~ .peer-hover\:underline { + text-decoration-line: underline; } - `) - }) + } + `) }) +}) - test('variants with slashes support modifiers', () => { - let config = { - content: [ - { - raw: html`
ar-1/10
`, - }, - ], - corePlugins: { preflight: false }, - plugins: [ - function ({ matchVariant }) { - matchVariant( - 'ar', - (value, { modifier }) => { - return [`@media (min-aspect-ratio: ${value}) and (foo: ${modifier})`] - }, - { values: { '1/10': '1/10' } } - ) - }, - ], +test('multi-class utilities handle selector-mutating variants correctly', () => { + let config = { + content: [ + { + raw: html`
`, + }, + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + @layer utilities { + .foo.bar.baz { + color: red; + } + .foo1 .bar1 .baz1 { + color: red; + } } + ` - let input = css` - @tailwind utilities; - ` + // The second set of ::after cases (w/ descendant selectors) + // are clearly "wrong" BUT you can't have a descendant of a + // pseudo - element so the utilities `after:foo1` and + // `after:bar1` are non-sensical so this is still + // perfectly fine behavior - return run(input, config).then((result) => { - stable.expect(result.css).toMatchFormattedCss(css` - @media (min-aspect-ratio: 1 / 10) and (foo: 20) { - .ar-1\/10\/20\:text-red-500 { - --tw-text-opacity: 1; - color: rgb(239 68 68 / var(--tw-text-opacity)); - } - } - `) - oxide.expect(result.css).toMatchFormattedCss(css` - @media (min-aspect-ratio: 1 / 10) and (foo: 20) { - .ar-1\/10\/20\:text-red-500 { - color: #ef4444; - } - } - `) - }) + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .after\:foo.bar.baz:after, + .after\:bar.foo.baz:after, + .after\:baz.foo.bar:after, + .after\:foo1 .bar1 .baz1:after, + .foo1 .after\:bar1 .baz1:after, + .foo1 .bar1 .after\:baz1:after { + content: var(--tw-content); + color: red; + } + .hover\:foo:hover.bar.baz, + .hover\:bar:hover.foo.baz, + .hover\:baz:hover.foo.bar, + .hover\:foo1:hover .bar1 .baz1, + .foo1 .hover\:bar1:hover .baz1, + .foo1 .bar1 .hover\:baz1:hover, + .group:hover .group-hover\:foo.bar.baz, + .group:hover .group-hover\:bar.foo.baz, + .group:hover .group-hover\:baz.foo.bar, + .group:hover .group-hover\:foo1 .bar1 .baz1, + .foo1 .group:hover .group-hover\:bar1 .baz1, + .foo1 .bar1 .group:hover .group-hover\:baz1, + .peer:checked ~ .peer-checked\:foo.bar.baz, + .peer:checked ~ .peer-checked\:bar.foo.baz, + .peer:checked ~ .peer-checked\:baz.foo.bar, + .peer:checked ~ .peer-checked\:foo1 .bar1 .baz1, + .foo1 .peer:checked ~ .peer-checked\:bar1 .baz1, + .foo1 .bar1 .peer:checked ~ .peer-checked\:baz1 { + color: red; + } + `) }) +}) - test('arbitrary variant selectors should not re-order scrollbar pseudo classes', async () => { - let config = { - content: [ - { - raw: html` -
-
-
-
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, +test('class inside pseudo-class function :has', () => { + let config = { + content: [ + { raw: html`
` }, + { raw: html`
` }, + { raw: html`
` }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + @layer utilities { + :where(.foo) { + color: red; + } + :is(.foo, .bar, .baz) { + color: orange; + } + :is(.foo) { + color: yellow; + } + html:has(.foo) { + color: green; + } } + ` - let input = css` - @tailwind utilities; - ` - - let result = await run(input, config) - + return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` - .\[\&\:\:-webkit-resizer\:hover\]\:underline::-webkit-resizer:hover { - text-decoration-line: underline; + :where(.foo) { + color: red; } - .\[\&\:\:-webkit-scrollbar-button\:hover\]\:underline::-webkit-scrollbar-button:hover { - text-decoration-line: underline; + :is(.foo, .bar, .baz) { + color: orange; } - .\[\&\:\:-webkit-scrollbar-corner\:hover\]\:underline::-webkit-scrollbar-corner:hover { - text-decoration-line: underline; + .foo { + color: #ff0; } - .\[\&\:\:-webkit-scrollbar-thumb\:hover\]\:underline::-webkit-scrollbar-thumb:hover { - text-decoration-line: underline; + html:has(.foo) { + color: green; } - .\[\&\:\:-webkit-scrollbar-track-piece\:hover\]\:underline::-webkit-scrollbar-track-piece:hover { - text-decoration-line: underline; + :where(.hover\:foo:hover) { + color: red; } - .\[\&\:\:-webkit-scrollbar-track\:hover\]\:underline::-webkit-scrollbar-track:hover { - text-decoration-line: underline; + :is(.hover\:foo:hover, .bar, .baz), + :is(.foo, .hover\:bar:hover, .baz), + :is(.foo, .bar, .hover\:baz:hover) { + color: orange; } - .\[\&\:\:-webkit-scrollbar\:hover\]\:underline::-webkit-scrollbar:hover { - text-decoration-line: underline; + .hover\:foo:hover { + color: #ff0; + } + html:has(.hover\:foo:hover) { + color: green; + } + @media (min-width: 640px) { + :where(.sm\:foo) { + color: red; + } + :is(.sm\:foo, .bar, .baz), + :is(.foo, .sm\:bar, .baz), + :is(.foo, .bar, .sm\:baz) { + color: orange; + } + .sm\:foo { + color: #ff0; + } + html:has(.sm\:foo) { + color: green; + } } `) }) +}) - test('stacking dark and rtl variants', async () => { - let config = { - darkMode: 'selector', - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, +test('variant functions returning arrays should output correct results when nesting', async () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + plugins: [ + function ({ addUtilities, addVariant }) { + addVariant('test', () => ['@media (test)']) + addUtilities({ + '.foo': { + display: 'grid', + '> *': { + 'grid-column': 'span 2', + }, + }, + }) + }, + ], + } + + let input = css` + @tailwind utilities; + ` + + let result = await run(input, config) + + expect(result.css).toMatchFormattedCss(css` + @media (test) { + .test\:foo { + display: grid; + } + .test\:foo > * { + grid-column: span 2; + } } + `) +}) - let input = css` - @tailwind utilities; - ` +test('variants with slashes in them work', () => { + let config = { + content: [ + { + raw: html`
ar-1/10
`, + }, + ], + theme: { + extend: { + screens: { + 'ar-1/10': { raw: '(min-aspect-ratio: 1/10)' }, + }, + }, + }, + corePlugins: { preflight: false }, + } - let result = await run(input, config) + let input = css` + @tailwind utilities; + ` + return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` - .dark\:rtl\:italic:where([dir='rtl'], [dir='rtl'] *):where(.dark, .dark *) { - font-style: italic; + @media (min-aspect-ratio: 1 / 10) { + .ar-1\/10\:text-red-500 { + --tw-text-opacity: 1; + color: rgb(239 68 68 / var(--tw-text-opacity)); + } } `) }) +}) - test('stacking dark and rtl variants with pseudo elements', async () => { - let config = { - darkMode: 'selector', - content: [ - { - raw: html`
`, - }, - ], - corePlugins: { preflight: false }, - } - - let input = css` - @tailwind utilities; - ` +test('variants with slashes support modifiers', () => { + let config = { + content: [ + { + raw: html`
ar-1/10
`, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + function ({ matchVariant }) { + matchVariant( + 'ar', + (value, { modifier }) => { + return [`@media (min-aspect-ratio: ${value}) and (foo: ${modifier})`] + }, + { values: { '1/10': '1/10' } } + ) + }, + ], + } - let result = await run(input, config) + let input = css` + @tailwind utilities; + ` + return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` - .dark\:rtl\:placeholder\:italic:where([dir='rtl'], [dir='rtl'] *):where( - .dark, - .dark * - )::placeholder { - font-style: italic; + @media (min-aspect-ratio: 1 / 10) and (foo: 20) { + .ar-1\/10\/20\:text-red-500 { + --tw-text-opacity: 1; + color: rgb(239 68 68 / var(--tw-text-opacity)); + } } `) }) +}) - test('* is matched by the parser as the children variant', async () => { - let config = { - content: [ - { - raw: html` -
-
-
-
-
- `, - }, - ], - corePlugins: { preflight: false }, +test('arbitrary variant selectors should not re-order scrollbar pseudo classes', async () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + let result = await run(input, config) + + expect(result.css).toMatchFormattedCss(css` + .\[\&\:\:-webkit-resizer\:hover\]\:underline::-webkit-resizer:hover { + text-decoration-line: underline; + } + .\[\&\:\:-webkit-scrollbar-button\:hover\]\:underline::-webkit-scrollbar-button:hover { + text-decoration-line: underline; + } + .\[\&\:\:-webkit-scrollbar-corner\:hover\]\:underline::-webkit-scrollbar-corner:hover { + text-decoration-line: underline; + } + .\[\&\:\:-webkit-scrollbar-thumb\:hover\]\:underline::-webkit-scrollbar-thumb:hover { + text-decoration-line: underline; + } + .\[\&\:\:-webkit-scrollbar-track-piece\:hover\]\:underline::-webkit-scrollbar-track-piece:hover { + text-decoration-line: underline; + } + .\[\&\:\:-webkit-scrollbar-track\:hover\]\:underline::-webkit-scrollbar-track:hover { + text-decoration-line: underline; + } + .\[\&\:\:-webkit-scrollbar\:hover\]\:underline::-webkit-scrollbar:hover { + text-decoration-line: underline; } + `) +}) - let input = css` - @tailwind utilities; - ` +test('stacking dark and rtl variants', async () => { + let config = { + darkMode: 'selector', + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + } - let result = await run(input, config) + let input = css` + @tailwind utilities; + ` - expect(result.css).toMatchFormattedCss(css` - .\*\:italic > *, - .\*\:hover\:italic:hover > *, - .hover\:\*\:italic > :hover, - .data-\[slot\=label\]\:\*\:hover\:italic:hover > [data-slot='label'], - .\[\&_p\]\:\*\:hover\:italic:hover > * p { - font-style: italic; - } - `) - }) + let result = await run(input, config) + + expect(result.css).toMatchFormattedCss(css` + .dark\:rtl\:italic:where([dir='rtl'], [dir='rtl'] *):where(.dark, .dark *) { + font-style: italic; + } + `) +}) + +test('stacking dark and rtl variants with pseudo elements', async () => { + let config = { + darkMode: 'selector', + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + let result = await run(input, config) + + expect(result.css).toMatchFormattedCss(css` + .dark\:rtl\:placeholder\:italic:where([dir='rtl'], [dir='rtl'] *):where( + .dark, + .dark * + )::placeholder { + font-style: italic; + } + `) +}) + +test('* is matched by the parser as the children variant', async () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+
+ `, + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + let result = await run(input, config) + + expect(result.css).toMatchFormattedCss(css` + .\*\:italic > *, + .\*\:hover\:italic:hover > *, + .hover\:\*\:italic > :hover, + .data-\[slot\=label\]\:\*\:hover\:italic:hover > [data-slot='label'], + .\[\&_p\]\:\*\:hover\:italic:hover > * p { + font-style: italic; + } + `) }) diff --git a/tests/warnings.test.js b/tests/warnings.test.js index e3ea94fe552d..28d2551c53f5 100644 --- a/tests/warnings.test.js +++ b/tests/warnings.test.js @@ -1,62 +1,60 @@ -import { crosscheck, run, html, css } from './util/run' +import { run, html, css } from './util/run' -crosscheck(() => { - test('it warns when there is no content key', async () => { - let config = { - corePlugins: { preflight: false }, - } +test('it warns when there is no content key', async () => { + let config = { + corePlugins: { preflight: false }, + } - let input = css` - @tailwind base; - ` + let input = css` + @tailwind base; + ` - await run(input, config) + await run(input, config) - expect().toHaveBeenWarnedWith(['content-problems']) - }) + expect().toHaveBeenWarnedWith(['content-problems']) +}) - test('it warns when there is an empty content key', async () => { - let config = { - content: [], - corePlugins: { preflight: false }, - } +test('it warns when there is an empty content key', async () => { + let config = { + content: [], + corePlugins: { preflight: false }, + } - let input = css` - @tailwind base; - ` + let input = css` + @tailwind base; + ` - await run(input, config) + await run(input, config) - expect().toHaveBeenWarnedWith(['content-problems']) - }) + expect().toHaveBeenWarnedWith(['content-problems']) +}) - test('it warns when there are no utilities generated', async () => { - let config = { - content: [{ raw: html`nothing here matching a utility` }], - corePlugins: { preflight: false }, - } +test('it warns when there are no utilities generated', async () => { + let config = { + content: [{ raw: html`nothing here matching a utility` }], + corePlugins: { preflight: false }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - await run(input, config) + await run(input, config) - expect().toHaveBeenWarnedWith(['content-problems']) - }) + expect().toHaveBeenWarnedWith(['content-problems']) +}) - it('warnings are not thrown when only variant utilities are generated', async () => { - let config = { - content: [{ raw: html`
` }], - corePlugins: { preflight: false }, - } +it('warnings are not thrown when only variant utilities are generated', async () => { + let config = { + content: [{ raw: html`
` }], + corePlugins: { preflight: false }, + } - let input = css` - @tailwind utilities; - ` + let input = css` + @tailwind utilities; + ` - await run(input, config) + await run(input, config) - expect().not.toHaveBeenWarned() - }) + expect().not.toHaveBeenWarned() }) diff --git a/tests/withAlphaVariable.test.js b/tests/withAlphaVariable.test.js index 29d39c7aaf84..d543dfc6ed73 100644 --- a/tests/withAlphaVariable.test.js +++ b/tests/withAlphaVariable.test.js @@ -1,243 +1,240 @@ import withAlphaVariable from '../src/util/withAlphaVariable' -import { crosscheck } from './util/run' -crosscheck(() => { - test('it adds the right custom property', () => { - expect( - withAlphaVariable({ color: '#ff0000', property: 'color', variable: '--tw-text-opacity' }) - ).toEqual({ - '--tw-text-opacity': '1', - color: 'rgb(255 0 0 / var(--tw-text-opacity))', - }) - expect( - withAlphaVariable({ - color: 'hsl(240 100% 50%)', - property: 'color', - variable: '--tw-text-opacity', - }) - ).toEqual({ - '--tw-text-opacity': '1', - color: 'hsl(240 100% 50% / var(--tw-text-opacity))', - }) +test('it adds the right custom property', () => { + expect( + withAlphaVariable({ color: '#ff0000', property: 'color', variable: '--tw-text-opacity' }) + ).toEqual({ + '--tw-text-opacity': '1', + color: 'rgb(255 0 0 / var(--tw-text-opacity))', + }) + expect( + withAlphaVariable({ + color: 'hsl(240 100% 50%)', + property: 'color', + variable: '--tw-text-opacity', + }) + ).toEqual({ + '--tw-text-opacity': '1', + color: 'hsl(240 100% 50% / var(--tw-text-opacity))', }) +}) - test('it ignores colors that cannot be parsed', () => { - expect( - withAlphaVariable({ - color: 'currentColor', - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - 'background-color': 'currentColor', - }) - expect( - withAlphaVariable({ - color: 'rgb(255, 0)', - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - 'background-color': 'rgb(255, 0)', - }) - expect( - withAlphaVariable({ - color: 'rgb(255)', - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - 'background-color': 'rgb(255)', - }) - expect( - withAlphaVariable({ - color: 'rgb(255, 0, 0, 255)', - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - 'background-color': 'rgb(255, 0, 0, 255)', - }) - expect( - withAlphaVariable({ - color: 'rgb(var(--color))', - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - 'background-color': 'rgb(var(--color))', - }) +test('it ignores colors that cannot be parsed', () => { + expect( + withAlphaVariable({ + color: 'currentColor', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + 'background-color': 'currentColor', + }) + expect( + withAlphaVariable({ + color: 'rgb(255, 0)', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + 'background-color': 'rgb(255, 0)', + }) + expect( + withAlphaVariable({ + color: 'rgb(255)', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + 'background-color': 'rgb(255)', + }) + expect( + withAlphaVariable({ + color: 'rgb(255, 0, 0, 255)', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + 'background-color': 'rgb(255, 0, 0, 255)', + }) + expect( + withAlphaVariable({ + color: 'rgb(var(--color))', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + 'background-color': 'rgb(var(--color))', }) +}) - test('it ignores colors that already have an alpha channel', () => { - expect( - withAlphaVariable({ - color: '#ff0000ff', - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - 'background-color': '#ff0000ff', - }) - expect( - withAlphaVariable({ - color: '#ff000080', - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - 'background-color': '#ff000080', - }) - expect( - withAlphaVariable({ - color: '#f00a', - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - 'background-color': '#f00a', - }) - expect( - withAlphaVariable({ - color: '#f00f', - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - 'background-color': '#f00f', - }) - expect( - withAlphaVariable({ - color: 'rgba(255, 255, 255, 1)', - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - 'background-color': 'rgba(255, 255, 255, 1)', - }) - expect( - withAlphaVariable({ - color: 'rgba(255, 255, 255, 0.5)', - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - 'background-color': 'rgba(255, 255, 255, 0.5)', - }) - expect( - withAlphaVariable({ - color: 'rgba(255 255 255 / 0.5)', - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - 'background-color': 'rgba(255 255 255 / 0.5)', - }) - expect( - withAlphaVariable({ - color: 'hsla(240, 100%, 50%, 1)', - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - 'background-color': 'hsla(240, 100%, 50%, 1)', - }) - expect( - withAlphaVariable({ - color: 'hsla(240, 100%, 50%, 0.5)', - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - 'background-color': 'hsla(240, 100%, 50%, 0.5)', - }) - expect( - withAlphaVariable({ - color: 'hsl(240 100% 50% / 0.5)', - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - 'background-color': 'hsl(240 100% 50% / 0.5)', - }) +test('it ignores colors that already have an alpha channel', () => { + expect( + withAlphaVariable({ + color: '#ff0000ff', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + 'background-color': '#ff0000ff', + }) + expect( + withAlphaVariable({ + color: '#ff000080', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + 'background-color': '#ff000080', + }) + expect( + withAlphaVariable({ + color: '#f00a', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + 'background-color': '#f00a', + }) + expect( + withAlphaVariable({ + color: '#f00f', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + 'background-color': '#f00f', + }) + expect( + withAlphaVariable({ + color: 'rgba(255, 255, 255, 1)', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + 'background-color': 'rgba(255, 255, 255, 1)', + }) + expect( + withAlphaVariable({ + color: 'rgba(255, 255, 255, 0.5)', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + 'background-color': 'rgba(255, 255, 255, 0.5)', }) + expect( + withAlphaVariable({ + color: 'rgba(255 255 255 / 0.5)', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + 'background-color': 'rgba(255 255 255 / 0.5)', + }) + expect( + withAlphaVariable({ + color: 'hsla(240, 100%, 50%, 1)', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + 'background-color': 'hsla(240, 100%, 50%, 1)', + }) + expect( + withAlphaVariable({ + color: 'hsla(240, 100%, 50%, 0.5)', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + 'background-color': 'hsla(240, 100%, 50%, 0.5)', + }) + expect( + withAlphaVariable({ + color: 'hsl(240 100% 50% / 0.5)', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + 'background-color': 'hsl(240 100% 50% / 0.5)', + }) +}) - test('it allows a closure to be passed', () => { - expect( - withAlphaVariable({ - color: ({ opacityVariable }) => `rgba(0, 0, 0, var(${opacityVariable}))`, - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - '--tw-bg-opacity': '1', - 'background-color': 'rgba(0, 0, 0, var(--tw-bg-opacity))', - }) - expect( - withAlphaVariable({ - color: ({ opacityValue }) => `rgba(0, 0, 0, ${opacityValue})`, - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - '--tw-bg-opacity': '1', - 'background-color': 'rgba(0, 0, 0, var(--tw-bg-opacity))', - }) +test('it allows a closure to be passed', () => { + expect( + withAlphaVariable({ + color: ({ opacityVariable }) => `rgba(0, 0, 0, var(${opacityVariable}))`, + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + '--tw-bg-opacity': '1', + 'background-color': 'rgba(0, 0, 0, var(--tw-bg-opacity))', + }) + expect( + withAlphaVariable({ + color: ({ opacityValue }) => `rgba(0, 0, 0, ${opacityValue})`, + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + '--tw-bg-opacity': '1', + 'background-color': 'rgba(0, 0, 0, var(--tw-bg-opacity))', }) +}) - test('it transforms rgb and hsl to space-separated rgb and hsl', () => { - expect( - withAlphaVariable({ - color: 'rgb(50, 50, 50)', - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - '--tw-bg-opacity': '1', - 'background-color': 'rgb(50 50 50 / var(--tw-bg-opacity))', - }) - expect( - withAlphaVariable({ - color: 'rgb(50 50 50)', - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - '--tw-bg-opacity': '1', - 'background-color': 'rgb(50 50 50 / var(--tw-bg-opacity))', - }) - expect( - withAlphaVariable({ - color: 'hsl(50, 50%, 50%)', - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - '--tw-bg-opacity': '1', - 'background-color': 'hsl(50 50% 50% / var(--tw-bg-opacity))', - }) - expect( - withAlphaVariable({ - color: 'hsl(50 50% 50%)', - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - '--tw-bg-opacity': '1', - 'background-color': 'hsl(50 50% 50% / var(--tw-bg-opacity))', - }) +test('it transforms rgb and hsl to space-separated rgb and hsl', () => { + expect( + withAlphaVariable({ + color: 'rgb(50, 50, 50)', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + '--tw-bg-opacity': '1', + 'background-color': 'rgb(50 50 50 / var(--tw-bg-opacity))', + }) + expect( + withAlphaVariable({ + color: 'rgb(50 50 50)', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + '--tw-bg-opacity': '1', + 'background-color': 'rgb(50 50 50 / var(--tw-bg-opacity))', + }) + expect( + withAlphaVariable({ + color: 'hsl(50, 50%, 50%)', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + '--tw-bg-opacity': '1', + 'background-color': 'hsl(50 50% 50% / var(--tw-bg-opacity))', }) + expect( + withAlphaVariable({ + color: 'hsl(50 50% 50%)', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + '--tw-bg-opacity': '1', + 'background-color': 'hsl(50 50% 50% / var(--tw-bg-opacity))', + }) +}) - test('it transforms named colors to rgb', () => { - expect( - withAlphaVariable({ - color: 'red', - property: 'background-color', - variable: '--tw-bg-opacity', - }) - ).toEqual({ - '--tw-bg-opacity': '1', - 'background-color': 'rgb(255 0 0 / var(--tw-bg-opacity))', - }) +test('it transforms named colors to rgb', () => { + expect( + withAlphaVariable({ + color: 'red', + property: 'background-color', + variable: '--tw-bg-opacity', + }) + ).toEqual({ + '--tw-bg-opacity': '1', + 'background-color': 'rgb(255 0 0 / var(--tw-bg-opacity))', }) }) diff --git a/turbo.json b/turbo.json index ef1c9043d139..fdd5ea33b272 100644 --- a/turbo.json +++ b/turbo.json @@ -3,14 +3,12 @@ "pipeline": { "//#build": { "outputs": ["lib/**", "peers/**", "types/generated/**"], - "inputs": ["src/**", "oxide/**", "nesting/**", "oxide-node-api-shim/**", "types/**"] + "inputs": ["src/**", "nesting/**", "types/**"] }, "test": { - "env": ["OXIDE"], "dependsOn": ["//#build"] }, "//#test": { - "env": ["OXIDE"], "dependsOn": ["//#build"], "inputs": ["tests/**"] },