Copy JIT over, tests passing separately, not yet integrated

This commit is contained in:
Adam Wathan 2021-04-02 11:16:15 -04:00
parent 60b5d63b19
commit bc5a82a4e3
195 changed files with 9932 additions and 9 deletions

View file

@ -0,0 +1,55 @@
const prettier = require('prettier')
const diff = require('jest-diff').default
function format(input) {
return prettier.format(input, {
parser: 'css',
printWidth: 100,
})
}
expect.extend({
// Compare two CSS strings with all whitespace removed
// This is probably naive but it's fast and works well enough.
toMatchCss(received, argument) {
const options = {
comment: 'stripped(received) === stripped(argument)',
isNot: this.isNot,
promise: this.promise,
}
let formattedReceived = format(received)
let formattedArgument = format(argument)
const pass = formattedReceived === formattedArgument
const message = pass
? () => {
return (
this.utils.matcherHint('toMatchCss', undefined, undefined, options) +
'\n\n' +
`Expected: not ${this.utils.printExpected(formattedReceived)}\n` +
`Received: ${this.utils.printReceived(formattedArgument)}`
)
}
: () => {
const actual = formattedReceived
const expected = formattedArgument
const diffString = diff(expected, actual, {
expand: this.expand,
})
return (
this.utils.matcherHint('toMatchCss', undefined, undefined, options) +
'\n\n' +
(diffString && diffString.includes('- Expect')
? `Difference:\n\n${diffString}`
: `Expected: ${this.utils.printExpected(expected)}\n` +
`Received: ${this.utils.printReceived(actual)}`)
)
}
return { actual: received, message, pass }
},
})

341
jit/tests/apply.test.css Normal file
View file

@ -0,0 +1,341 @@
* {
--tw-shadow: 0 0 #0000;
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgba(59, 130, 246, 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
}
.basic-example {
border-radius: 0.375rem;
--tw-bg-opacity: 1;
background-color: rgba(59, 130, 246, var(--tw-bg-opacity));
padding-left: 1rem;
padding-right: 1rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.class-order {
padding: 2rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
padding-top: 1.75rem;
padding-bottom: 1.75rem;
padding-top: 1rem;
padding-right: 0.25rem;
}
.with-additional-properties {
font-weight: 500;
text-align: right;
}
.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;
}
}
.dark .apply-dark-variant {
text-align: center;
}
.dark .apply-dark-variant:hover {
text-align: right;
}
@media (min-width: 1024px) {
.dark .apply-dark-variant {
text-align: left;
}
}
.apply-custom-utility {
custom: stuff;
}
.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 {
border-radius: 0.375rem;
--tw-bg-opacity: 1;
background-color: rgba(59, 130, 246, var(--tw-bg-opacity));
padding-left: 1rem;
padding-right: 1rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.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;
}
}
/* TODO: This works but the generated CSS is unnecessarily verbose. */
.complex-utilities {
--tw-ordinal: var(--tw-empty, /*!*/ /*!*/);
--tw-slashed-zero: var(--tw-empty, /*!*/ /*!*/);
--tw-numeric-figure: var(--tw-empty, /*!*/ /*!*/);
--tw-numeric-spacing: var(--tw-empty, /*!*/ /*!*/);
--tw-numeric-fraction: var(--tw-empty, /*!*/ /*!*/);
font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
var(--tw-numeric-spacing) var(--tw-numeric-fraction);
--tw-ordinal: var(--tw-empty, /*!*/ /*!*/);
--tw-slashed-zero: var(--tw-empty, /*!*/ /*!*/);
--tw-numeric-figure: var(--tw-empty, /*!*/ /*!*/);
--tw-numeric-spacing: var(--tw-empty, /*!*/ /*!*/);
--tw-numeric-fraction: var(--tw-empty, /*!*/ /*!*/);
font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
var(--tw-numeric-spacing) var(--tw-numeric-fraction);
--tw-ordinal: ordinal;
--tw-numeric-spacing: tabular-nums;
--tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
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 rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.complex-utilities:focus {
--tw-ordinal: var(--tw-empty, /*!*/ /*!*/);
--tw-slashed-zero: var(--tw-empty, /*!*/ /*!*/);
--tw-numeric-figure: var(--tw-empty, /*!*/ /*!*/);
--tw-numeric-spacing: var(--tw-empty, /*!*/ /*!*/);
--tw-numeric-fraction: var(--tw-empty, /*!*/ /*!*/);
font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
var(--tw-numeric-spacing) var(--tw-numeric-fraction);
--tw-numeric-fraction: diagonal-fractions;
}
.basic-nesting-parent {
.basic-nesting-child {
font-weight: 700;
}
.basic-nesting-child:hover {
font-weight: 400;
}
}
.use-base-only-a {
font-weight: 700;
}
.use-dependant-only-b {
font-weight: 700;
font-weight: 400;
}
.btn {
border-radius: 0.25rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
padding-left: 1rem;
padding-right: 1rem;
font-weight: 700;
}
.btn-blue {
border-radius: 0.25rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
padding-left: 1rem;
padding-right: 1rem;
font-weight: 700;
--tw-bg-opacity: 1;
background-color: rgba(59, 130, 246, var(--tw-bg-opacity));
--tw-text-opacity: 1;
color: rgba(255, 255, 255, var(--tw-text-opacity));
}
.btn-blue:hover {
--tw-bg-opacity: 1;
background-color: rgba(29, 78, 216, var(--tw-bg-opacity));
}
.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 {
color: green;
font-weight: 700;
}
.use-with-other-properties-component {
color: green;
font-weight: 700;
}
.add-sibling-properties {
padding: 2rem;
padding-left: 1rem;
padding-right: 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 {
padding-top: 3px;
color: green;
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;
}
}
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;
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.foo {
animation: spin 1s linear infinite;
}
@keyframes pulse {
50% {
opacity: 0.5;
}
}
.bar {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite !important;
}

39
jit/tests/apply.test.html Normal file
View file

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<link rel="stylesheet" href="./tailwind.css" />
</head>
<body>
<div class="basic-example"></div>
<div class="class-order"></div>
<div class="with-additional-properties"></div>
<div class="variants"></div>
<div class="only-variants"></div>
<div class="apply-group-variant"></div>
<div class="apply-dark-variant"></div>
<div class="apply-custom-utility"></div>
<div class="multiple selectors"></div>
<div class="multiple-variants selectors-variants"></div>
<div class="multiple-group selectors-group"></div>
<div class="complex-utilities"></div>
<div class="basic-nesting-parent">
<div class="basic-nesting-child"></div>
</div>
<div class="use-base-only-a"></div>
<div class="use-dependant-only-b"></div>
<div class="btn"></div>
<div class="btn-blue"></div>
<div class="recursive-apply-a"></div>
<div class="recursive-apply-b"></div>
<div class="recursive-apply-c"></div>
<div class="use-with-other-properties-base use-with-other-properties-component"></div>
<div class="add-sibling-properties"></div>
<div class="a b"></div>
<div class="foo"></div>
<div class="bar"></div>
</body>
</html>

218
jit/tests/apply.test.js Normal file
View file

@ -0,0 +1,218 @@
const postcss = require('postcss')
const tailwind = require('../index.js')
const fs = require('fs')
const path = require('path')
function run(input, config = {}) {
const { currentTestName } = expect.getState()
return postcss([tailwind(config)]).process(input, {
from: `${path.resolve(__filename)}?test=${currentTestName}`,
})
}
test('@apply', () => {
let config = {
darkMode: 'class',
purge: [path.resolve(__dirname, './apply.test.html')],
corePlugins: { preflight: false },
theme: {},
plugins: [],
}
let css = `
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.basic-example {
@apply px-4 py-2 bg-blue-500 rounded-md;
}
.class-order {
@apply pt-4 pr-1 px-3 py-7 p-8;
}
.with-additional-properties {
font-weight: 500;
@apply text-right;
}
.variants {
@apply xl:focus:font-black hover:font-bold lg:font-light focus:font-medium font-semibold;
}
.only-variants {
@apply xl:focus:font-black hover:font-bold lg:font-light focus:font-medium;
}
.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 px-4 py-2 bg-blue-500 rounded-md;
}
.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;
}
/* TODO: This works but the generated CSS is unnecessarily verbose. */
.complex-utilities {
@apply ordinal tabular-nums focus:diagonal-fractions shadow-lg hover:shadow-xl;
}
.basic-nesting-parent {
.basic-nesting-child {
@apply font-bold hover:font-normal;
}
}
.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 font-bold py-2 px-4 rounded;
}
.btn-blue {
@apply btn bg-blue-500 hover:bg-blue-700 text-white;
}
.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 lg:text-2xl sm:text-3xl;
}
h2 {
@apply text-2xl;
@apply lg:text-2xl;
@apply sm:text-2xl;
}
}
@layer utilities {
.custom-util {
custom: stuff;
}
.foo {
@apply animate-spin;
}
.bar {
@apply animate-pulse !important;
}
}
`
return run(css, config).then((result) => {
let expectedPath = path.resolve(__dirname, './apply.test.css')
let expected = fs.readFileSync(expectedPath, 'utf8')
expect(result.css).toMatchCss(expected)
})
})
test('@apply error with unknown utility', async () => {
let config = {
darkMode: 'class',
purge: [path.resolve(__dirname, './apply.test.html')],
corePlugins: { preflight: false },
plugins: [],
}
let css = `
@tailwind components;
@tailwind utilities;
@layer components {
.foo {
@apply a-utility-that-does-not-exist;
}
}
`
await expect(run(css, config)).rejects.toThrowError('class does not exist')
})
test('@apply error with nested @screen', async () => {
let config = {
darkMode: 'class',
purge: [path.resolve(__dirname, './apply.test.html')],
corePlugins: { preflight: false },
plugins: [],
}
let css = `
@tailwind components;
@tailwind utilities;
@layer components {
.foo {
@screen md {
@apply text-black;
}
}
}
`
await expect(run(css, config)).rejects.toThrowError(
'@apply is not supported within nested at-rules like @screen'
)
})
test('@apply error with nested @anyatrulehere', async () => {
let config = {
darkMode: 'class',
purge: [path.resolve(__dirname, './apply.test.html')],
corePlugins: { preflight: false },
plugins: [],
}
let css = `
@tailwind components;
@tailwind utilities;
@layer components {
.foo {
@genie {
@apply text-black;
}
}
}
`
await expect(run(css, config)).rejects.toThrowError(
'@apply is not supported within nested at-rules like @genie'
)
})

View file

@ -0,0 +1,149 @@
* {
--tw-shadow: 0 0 #0000;
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgba(59, 130, 246, 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
}
.inset-\[11px\] {
top: 11px;
right: 11px;
bottom: 11px;
left: 11px;
}
.mt-\[clamp\(30px\2c 100px\)\] {
margin-top: clamp(30px, 100px);
}
.h-\[3\.23rem\] {
height: 3.23rem;
}
.h-\[calc\(100\%\+1rem\)\] {
height: calc(100% + 1rem);
}
.h-\[var\(--height\)\] {
height: var(--height);
}
.max-h-\[3\.23rem\] {
max-height: 3.23rem;
}
.max-h-\[calc\(100\%\+1rem\)\] {
max-height: calc(100% + 1rem);
}
.max-h-\[var\(--height\)\] {
max-height: var(--height);
}
.min-h-\[3\.23rem\] {
min-height: 3.23rem;
}
.min-h-\[calc\(100\%\+1rem\)\] {
min-height: calc(100% + 1rem);
}
.min-h-\[var\(--height\)\] {
min-height: var(--height);
}
.w-\[3\.23rem\] {
width: 3.23rem;
}
.w-\[calc\(100\%\+1rem\)\] {
width: calc(100% + 1rem);
}
.w-\[var\(--width\)\] {
width: var(--width);
}
.min-w-\[3\.23rem\] {
min-width: 3.23rem;
}
.min-w-\[calc\(100\%\+1rem\)\] {
min-width: calc(100% + 1rem);
}
.min-w-\[var\(--width\)\] {
min-width: var(--width);
}
.max-w-\[3\.23rem\] {
max-width: 3.23rem;
}
.max-w-\[calc\(100\%\+1rem\)\] {
max-width: calc(100% + 1rem);
}
.max-w-\[var\(--width\)\] {
max-width: var(--width);
}
.rotate-\[23deg\] {
--tw-rotate: 23deg;
}
.rotate-\[2\.3rad\] {
--tw-rotate: 2.3rad;
}
.rotate-\[401grad\] {
--tw-rotate: 401grad;
}
.rotate-\[1\.5turn\] {
--tw-rotate: 1.5turn;
}
.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;
}
.space-x-\[20cm\] > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(20cm * var(--tw-space-x-reverse));
margin-left: calc(20cm * calc(1 - var(--tw-space-x-reverse)));
}
.space-x-\[calc\(20\%-1cm\)\] > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(calc(20% - 1cm) * var(--tw-space-x-reverse));
margin-left: calc(calc(20% - 1cm) * calc(1 - var(--tw-space-x-reverse)));
}
.border-\[2\.5px\] {
border-width: 2.5px;
}
.border-\[\#f00\] {
--tw-border-opacity: 1;
border-color: rgba(255, 0, 0, var(--tw-border-opacity));
}
.bg-\[\#0f0\] {
--tw-bg-opacity: 1;
background-color: rgba(0, 255, 0, var(--tw-bg-opacity));
}
.bg-\[\#ff0000\] {
--tw-bg-opacity: 1;
background-color: rgba(255, 0, 0, var(--tw-bg-opacity));
}
.bg-\[\#0000ffcc\] {
background-color: #0000ffcc;
}
.bg-\[rgb\(123\2c 123\2c 123\)\] {
--tw-bg-opacity: 1;
background-color: rgba(123, 123, 123, var(--tw-bg-opacity));
}
.bg-\[rgba\(123\2c 123\2c 123\2c 0\.5\)\] {
background-color: rgba(123, 123, 123, 0.5);
}
.bg-\[hsl\(0\2c 100\%\2c 50\%\)\] {
--tw-bg-opacity: 1;
background-color: rgba(255, 0, 0, var(--tw-bg-opacity));
}
.bg-\[hsla\(0\2c 100\%\2c 50\%\2c 0\.3\)\] {
background-color: hsla(0, 100%, 50%, 0.3);
}
.bg-opacity-\[0\.11\] {
--tw-bg-opacity: 0.11;
}
.p-\[var\(--app-padding\)\] {
padding: var(--app-padding);
}
.text-\[2\.23rem\] {
font-size: 2.23rem;
}
.duration-\[2s\] {
transition-duration: 2s;
}
.duration-\[var\(--app-duration\)\] {
transition-duration: var(--app-duration);
}
@media (min-width: 1024px) {
.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;
}
}

View file

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<link rel="stylesheet" href="./tailwind.css" />
</head>
<body>
<div class="bg-[#0f0] bg-[#ff0000] bg-[#0000ffcc]"></div>
<div class="bg-[rgb(123,123,123)] bg-[rgba(123,123,123,0.5)]"></div>
<div class="bg-[hsl(0,100%,50%)] bg-[hsla(0,100%,50%,0.3)]"></div>
<div class="bg-opacity-[0.11]"></div>
<div class="border-[#f00]"></div>
<div class="border-[2.5px]"></div>
<div class="w-[3.23rem]"></div>
<div class="w-[calc(100%+1rem)]"></div>
<div class="w-[var(--width)]"></div>
<div class="min-w-[3.23rem]"></div>
<div class="min-w-[calc(100%+1rem)]"></div>
<div class="min-w-[var(--width)]"></div>
<div class="max-w-[3.23rem]"></div>
<div class="max-w-[calc(100%+1rem)]"></div>
<div class="max-w-[var(--width)]"></div>
<div class="h-[3.23rem]"></div>
<div class="h-[calc(100%+1rem)]"></div>
<div class="h-[var(--height)]"></div>
<div class="min-h-[3.23rem]"></div>
<div class="min-h-[calc(100%+1rem)]"></div>
<div class="min-h-[var(--height)]"></div>
<div class="max-h-[3.23rem]"></div>
<div class="max-h-[calc(100%+1rem)]"></div>
<div class="max-h-[var(--height)]"></div>
<div class="space-x-[20cm]"></div>
<div class="space-x-[calc(20%-1cm)]"></div>
<div class="grid-cols-[200px,repeat(auto-fill,minmax(15%,100px)),300px]"></div>
<div class="lg:grid-cols-[200px,repeat(auto-fill,minmax(15%,100px)),300px]"></div>
<div class="rotate-[23deg] rotate-[2.3rad] rotate-[401grad] rotate-[1.5turn]"></div>
<div class="text-[2.23rem]"></div>
<div class="duration-[2s]"></div>
<div class="mt-[clamp(30px,100px)]"></div>
<div class="duration-[var(--app-duration)]"></div>
<div class="p-[var(--app-padding)]"></div>
<div class="inset-[11px]"></div>
</body>
</html>

View file

@ -0,0 +1,30 @@
const postcss = require('postcss')
const tailwind = require('../index.js')
const fs = require('fs')
const path = require('path')
function run(input, config = {}) {
return postcss([tailwind(config)]).process(input, { from: path.resolve(__filename) })
}
test('arbitrary values', () => {
let config = {
purge: [path.resolve(__dirname, './arbitrary-values.test.html')],
corePlugins: { preflight: false },
theme: {},
plugins: [],
}
let css = `
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(css, config).then((result) => {
let expectedPath = path.resolve(__dirname, './arbitrary-values.test.css')
let expected = fs.readFileSync(expectedPath, 'utf8')
expect(result.css).toMatchCss(expected)
})
})

View file

@ -0,0 +1,631 @@
* {
--tw-shadow: 0 0 #0000;
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgba(59, 130, 246, 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
}
.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;
}
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.pointer-events-none {
pointer-events: none;
}
.invisible {
visibility: hidden;
}
.absolute {
position: absolute;
}
.inset-0 {
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
}
.inset-y-4 {
top: 1rem;
bottom: 1rem;
}
.inset-x-2 {
left: 0.5rem;
right: 0.5rem;
}
.top-6 {
top: 1.5rem;
}
.right-8 {
right: 2rem;
}
.bottom-12 {
bottom: 3rem;
}
.left-16 {
left: 4rem;
}
.z-30 {
z-index: 30;
}
.order-last {
order: 9999;
}
.order-2 {
order: 2;
}
.col-span-3 {
grid-column: span 3 / span 3;
}
.col-end-4 {
grid-column-end: 4;
}
.col-start-1 {
grid-column-start: 1;
}
.row-span-2 {
grid-row: span 2 / span 2;
}
.row-end-5 {
grid-row-end: 5;
}
.row-start-3 {
grid-row-start: 3;
}
.float-right {
float: right;
}
.clear-left {
clear: left;
}
.m-4 {
margin: 1rem;
}
.my-2 {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
.mx-auto {
margin-left: auto;
margin-right: auto;
}
.mt-0 {
margin-top: 0px;
}
.mr-1 {
margin-right: 0.25rem;
}
.mb-3 {
margin-bottom: 0.75rem;
}
.ml-4 {
margin-left: 1rem;
}
.box-border {
box-sizing: border-box;
}
.inline-grid {
display: inline-grid;
}
.hidden {
display: none;
}
.h-16 {
height: 4rem;
}
.max-h-screen {
max-height: 100vh;
}
.min-h-0 {
min-height: 0px;
}
.w-12 {
width: 3rem;
}
.min-w-min {
min-width: min-content;
}
.max-w-full {
max-width: 100%;
}
.flex-1 {
flex: 1 1 0%;
}
.flex-shrink {
flex-shrink: 1;
}
.flex-shrink-0 {
flex-shrink: 0;
}
.flex-grow {
flex-grow: 1;
}
.flex-grow-0 {
flex-grow: 0;
}
.table-fixed {
table-layout: fixed;
}
.border-collapse {
border-collapse: collapse;
}
.transform {
--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;
transform: translateX(var(--tw-translate-x)) translateY(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-gpu {
--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;
transform: translate3d(var(--tw-translate-x), var(--tw-translate-y), 0) rotate(var(--tw-rotate))
skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
scaleY(var(--tw-scale-y));
}
.origin-top-right {
transform-origin: top right;
}
.translate-x-5 {
--tw-translate-x: 1.25rem;
}
.-translate-x-4 {
--tw-translate-x: -1rem;
}
.translate-y-6 {
--tw-translate-y: 1.5rem;
}
.-translate-x-3 {
--tw-translate-x: -0.75rem;
}
.rotate-3 {
--tw-rotate: 3deg;
}
.skew-y-12 {
--tw-skew-y: 12deg;
}
.skew-x-12 {
--tw-skew-x: 12deg;
}
.scale-95 {
--tw-scale-x: 0.95;
--tw-scale-y: 0.95;
}
.animate-none {
animation: none;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.animate-spin {
animation: spin 1s linear infinite;
}
.cursor-pointer {
cursor: pointer;
}
.select-none {
user-select: none;
}
.resize-none {
resize: none;
}
.list-inside {
list-style-position: inside;
}
.list-disc {
list-style-type: disc;
}
.appearance-none {
appearance: none;
}
.auto-cols-min {
grid-auto-columns: min-content;
}
.grid-flow-row {
grid-auto-flow: row;
}
.auto-rows-max {
grid-auto-rows: max-content;
}
.grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.grid-rows-3 {
grid-template-rows: repeat(3, minmax(0, 1fr));
}
.flex-row-reverse {
flex-direction: row-reverse;
}
.flex-wrap {
flex-wrap: wrap;
}
.place-content-start {
place-content: start;
}
.place-items-end {
place-items: end;
}
.content-center {
align-content: center;
}
.items-start {
align-items: flex-start;
}
.justify-center {
justify-content: center;
}
.justify-items-end {
justify-items: end;
}
.gap-4 {
gap: 1rem;
}
.gap-x-2 {
column-gap: 0.5rem;
}
.gap-y-3 {
row-gap: 0.75rem;
}
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(1rem * var(--tw-space-x-reverse));
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
}
.space-y-3 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(0.75rem * var(--tw-space-y-reverse));
}
.space-y-reverse > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 1;
}
.space-x-reverse > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 1;
}
.divide-x-2 > :not([hidden]) ~ :not([hidden]) {
--tw-divide-x-reverse: 0;
border-right-width: calc(2px * var(--tw-divide-x-reverse));
border-left-width: calc(2px * calc(1 - var(--tw-divide-x-reverse)));
}
.divide-y-4 > :not([hidden]) ~ :not([hidden]) {
--tw-divide-y-reverse: 0;
border-top-width: calc(4px * calc(1 - var(--tw-divide-y-reverse)));
border-bottom-width: calc(4px * var(--tw-divide-y-reverse));
}
.divide-x-0 > :not([hidden]) ~ :not([hidden]) {
--tw-divide-x-reverse: 0;
border-right-width: calc(0px * var(--tw-divide-x-reverse));
border-left-width: calc(0px * calc(1 - var(--tw-divide-x-reverse)));
}
.divide-y-0 > :not([hidden]) ~ :not([hidden]) {
--tw-divide-y-reverse: 0;
border-top-width: calc(0px * calc(1 - var(--tw-divide-y-reverse)));
border-bottom-width: calc(0px * var(--tw-divide-y-reverse));
}
.divide-dotted > :not([hidden]) ~ :not([hidden]) {
border-style: dotted;
}
.divide-gray-200 > :not([hidden]) ~ :not([hidden]) {
--tw-divide-opacity: 1;
border-color: rgba(229, 231, 235, var(--tw-divide-opacity));
}
.divide-opacity-50 > :not([hidden]) ~ :not([hidden]) {
--tw-divide-opacity: 0.5;
}
.place-self-center {
place-self: center;
}
.self-end {
align-self: flex-end;
}
.justify-self-start {
justify-self: start;
}
.overflow-hidden {
overflow: hidden;
}
.overscroll-contain {
overscroll-behavior: contain;
}
.truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.overflow-ellipsis {
text-overflow: ellipsis;
}
.whitespace-nowrap {
white-space: nowrap;
}
.break-words {
overflow-wrap: break-word;
}
.rounded-md {
border-radius: 0.375rem;
}
.border {
border-width: 1px;
}
.border-2 {
border-width: 2px;
}
.border-solid {
border-style: solid;
}
.border-black {
--tw-border-opacity: 1;
border-color: rgba(0, 0, 0, var(--tw-border-opacity));
}
.border-opacity-10 {
--tw-border-opacity: 0.1;
}
.bg-green-500 {
--tw-bg-opacity: 1;
background-color: rgba(16, 185, 129, var(--tw-bg-opacity));
}
.bg-opacity-20 {
--tw-bg-opacity: 0.2;
}
.bg-gradient-to-r {
background-image: linear-gradient(to right, var(--tw-gradient-stops));
}
.from-red-300 {
--tw-gradient-from: #fca5a5;
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgba(252, 165, 165, 0));
}
.via-purple-200 {
--tw-gradient-stops: var(--tw-gradient-from), #ddd6fe,
var(--tw-gradient-to, rgba(221, 214, 254, 0));
}
.to-blue-400 {
--tw-gradient-to: #60a5fa;
}
.bg-cover {
background-size: cover;
}
.bg-local {
background-attachment: local;
}
.bg-clip-border {
background-clip: border-box;
}
.bg-top {
background-position: top;
}
.bg-no-repeat {
background-repeat: no-repeat;
}
.fill-current {
fill: currentColor;
}
.stroke-current {
stroke: currentColor;
}
.stroke-2 {
stroke-width: 2;
}
.object-cover {
object-fit: cover;
}
.object-bottom {
object-position: bottom;
}
.p-4 {
padding: 1rem;
}
.py-2 {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
.px-3 {
padding-left: 0.75rem;
padding-right: 0.75rem;
}
.pt-1 {
padding-top: 0.25rem;
}
.pr-2 {
padding-right: 0.5rem;
}
.pb-3 {
padding-bottom: 0.75rem;
}
.pl-4 {
padding-left: 1rem;
}
.text-center {
text-align: center;
}
.align-middle {
vertical-align: middle;
}
.font-sans {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
}
.text-2xl {
font-size: 1.5rem;
line-height: 2rem;
}
.font-medium {
font-weight: 500;
}
.uppercase {
text-transform: uppercase;
}
.not-italic {
font-style: normal;
}
.ordinal,
.slashed-zero,
.lining-nums,
.oldstyle-nums,
.proportional-nums,
.tabular-nums,
.diagonal-fractions,
.stacked-fractions {
--tw-ordinal: var(--tw-empty, /*!*/ /*!*/);
--tw-slashed-zero: var(--tw-empty, /*!*/ /*!*/);
--tw-numeric-figure: var(--tw-empty, /*!*/ /*!*/);
--tw-numeric-spacing: var(--tw-empty, /*!*/ /*!*/);
--tw-numeric-fraction: var(--tw-empty, /*!*/ /*!*/);
font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
var(--tw-numeric-spacing) var(--tw-numeric-fraction);
}
.tabular-nums {
--tw-numeric-spacing: tabular-nums;
}
.ordinal {
--tw-ordinal: ordinal;
}
.diagonal-fractions {
--tw-numeric-fraction: diagonal-fractions;
}
.leading-relaxed {
line-height: 1.625;
}
.leading-5 {
line-height: 1.25rem;
}
.tracking-tight {
letter-spacing: -0.025em;
}
.text-indigo-500 {
--tw-text-opacity: 1;
color: rgba(99, 102, 241, var(--tw-text-opacity));
}
.text-opacity-10 {
--tw-text-opacity: 0.1;
}
.underline {
text-decoration: underline;
}
.antialiased {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.placeholder-green-300::placeholder {
--tw-placeholder-opacity: 1;
color: rgba(110, 231, 183, var(--tw-placeholder-opacity));
}
.placeholder-opacity-60::placeholder {
--tw-placeholder-opacity: 0.6;
}
.opacity-90 {
opacity: 0.9;
}
.shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.shadow-lg {
--tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.outline-none {
outline: 2px solid transparent;
outline-offset: 2px;
}
.outline-black {
outline: 2px dotted black;
outline-offset: 2px;
}
.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-4 {
--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(4px + 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-white {
--tw-ring-opacity: 1;
--tw-ring-color: rgba(255, 255, 255, var(--tw-ring-opacity));
}
.ring-opacity-40 {
--tw-ring-opacity: 0.4;
}
.ring-offset-2 {
--tw-ring-offset-width: 2px;
}
.ring-offset-blue-300 {
--tw-ring-offset-color: #93c5fd;
}
.transition {
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow,
transform;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.transition-all {
transition-property: all;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.delay-300 {
transition-delay: 300ms;
}
.duration-200 {
transition-duration: 200ms;
}
.ease-in-out {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

View file

@ -0,0 +1,135 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<link rel="stylesheet" href="./tailwind.css" />
</head>
<body>
<div class="sr-only"></div>
<div class="content-center"></div>
<div class="items-start"></div>
<div class="self-end"></div>
<div class="animate-none"></div>
<div class="animate-spin"></div>
<div class="appearance-none"></div>
<div class="bg-local"></div>
<div class="bg-clip-border"></div>
<div class="bg-green-500"></div>
<div class="bg-gradient-to-r"></div>
<div class="bg-opacity-20"></div>
<div class="bg-top"></div>
<div class="bg-no-repeat"></div>
<div class="bg-cover"></div>
<div class="border-collapse"></div>
<div class="border-black"></div>
<div class="border-opacity-10"></div>
<div class="rounded-md"></div>
<div class="border-solid"></div>
<div class="border"></div>
<div class="border-2"></div>
<div class="shadow-md"></div>
<div class="shadow-lg"></div>
<div class="box-border"></div>
<div class="clear-left"></div>
<div class="container"></div>
<div class="cursor-pointer"></div>
<div class="hidden inline-grid"></div>
<div class="divide-gray-200"></div>
<div class="divide-opacity-50"></div>
<div class="divide-dotted"></div>
<div class="divide-x-2 divide-y-4 divide-x-0 divide-y-0"></div>
<div class="fill-current"></div>
<div class="flex-1"></div>
<div class="flex-row-reverse"></div>
<div class="flex-grow"></div>
<div class="flex-grow-0"></div>
<div class="flex-shrink"></div>
<div class="flex-shrink-0"></div>
<div class="flex-wrap"></div>
<div class="float-right"></div>
<div class="font-sans"></div>
<div class="text-2xl"></div>
<div class="antialiased"></div>
<div class="not-italic"></div>
<div class="tabular-nums ordinal diagonal-fractions"></div>
<div class="font-medium"></div>
<div class="gap-x-2 gap-y-3 gap-4"></div>
<div class="from-red-300 via-purple-200 to-blue-400"></div>
<div class="auto-cols-min"></div>
<div class="grid-flow-row"></div>
<div class="auto-rows-max"></div>
<div class="col-span-3"></div>
<div class="col-start-1"></div>
<div class="col-end-4"></div>
<div class="row-span-2"></div>
<div class="row-start-3"></div>
<div class="row-end-5"></div>
<div class="grid-cols-4"></div>
<div class="grid-rows-3"></div>
<div class="h-16"></div>
<div class="inset-0 inset-y-4 inset-x-2 top-6 right-8 bottom-12 left-16"></div>
<div class="justify-center"></div>
<div class="justify-items-end"></div>
<div class="justify-self-start"></div>
<div class="tracking-tight"></div>
<div class="leading-relaxed leading-5"></div>
<div class="list-inside"></div>
<div class="list-disc"></div>
<div class="m-4 my-2 mx-auto mt-0 mr-1 mb-3 ml-4"></div>
<div class="max-h-screen"></div>
<div class="max-w-full"></div>
<div class="min-h-0"></div>
<div class="min-w-min"></div>
<div class="object-cover"></div>
<div class="object-bottom"></div>
<div class="opacity-90"></div>
<div class="order-last order-2"></div>
<div class="outline-none outline-black"></div>
<div class="overflow-hidden"></div>
<div class="overscroll-contain"></div>
<div class="p-4 py-2 px-3 pt-1 pr-2 pb-3 pl-4"></div>
<div class="place-content-start"></div>
<div class="placeholder-green-300"></div>
<div class="placeholder-opacity-60"></div>
<div class="place-items-end"></div>
<div class="place-self-center"></div>
<div class="pointer-events-none"></div>
<div class="absolute"></div>
<div class="resize-none"></div>
<div class="ring-white"></div>
<div class="ring-offset-blue-300"></div>
<div class="ring-offset-2"></div>
<div class="ring-opacity-40"></div>
<div class="ring ring-4"></div>
<div class="rotate-3"></div>
<div class="scale-95"></div>
<div class="skew-y-12 skew-x-12"></div>
<div class="space-x-4 space-y-3 space-x-reverse space-y-reverse"></div>
<div class="stroke-current"></div>
<div class="stroke-2"></div>
<div class="table-fixed"></div>
<div class="text-center"></div>
<div class="text-indigo-500"></div>
<div class="underline"></div>
<div class="text-opacity-10"></div>
<div class="overflow-ellipsis truncate"></div>
<div class="uppercase"></div>
<div class="transform transform-gpu"></div>
<div class="origin-top-right"></div>
<div class="delay-300"></div>
<div class="duration-200"></div>
<div class="transition transition-all"></div>
<div class="ease-in-out"></div>
<div class="translate-x-5 -translate-x-4 translate-y-6 -translate-x-3"></div>
<div class="select-none"></div>
<div class="align-middle"></div>
<div class="invisible"></div>
<div class="whitespace-nowrap"></div>
<div class="w-12"></div>
<div class="break-words"></div>
<div class="z-30"></div>
</body>
</html>

View file

@ -0,0 +1,30 @@
const postcss = require('postcss')
const tailwind = require('../index.js')
const fs = require('fs')
const path = require('path')
function run(input, config = {}) {
return postcss([tailwind(config)]).process(input, { from: path.resolve(__filename) })
}
test('basic usage', () => {
let config = {
purge: [path.resolve(__dirname, './basic-usage.test.html')],
corePlugins: { preflight: false },
theme: {},
plugins: [],
}
let css = `
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(css, config).then((result) => {
let expectedPath = path.resolve(__dirname, './basic-usage.test.css')
let expected = fs.readFileSync(expectedPath, 'utf8')
expect(result.css).toMatchCss(expected)
})
})

View file

@ -0,0 +1,56 @@
* {
--tw-shadow: 0 0 #0000;
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgba(59, 130, 246, 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
}
@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');
}
@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;
}
.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;
}
}

View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<link rel="stylesheet" href="./tailwind.css" />
</head>
<body>
<div class="custom-component"></div>
<div class="sm:custom-component"></div>
<div class="md:custom-component"></div>
<div class="lg:custom-component"></div>
<div class="font-bold"></div>
<div class="sm:font-bold"></div>
<div class="sm:text-center"></div>
<div class="md:font-bold"></div>
<div class="md:text-center"></div>
<div class="lg:font-bold"></div>
<div class="lg:text-center"></div>
</body>
</html>

View file

@ -0,0 +1,55 @@
const postcss = require('postcss')
const tailwind = require('../index.js')
const fs = require('fs')
const path = require('path')
function run(input, config = {}) {
return postcss([tailwind(config)]).process(input, { from: path.resolve(__filename) })
}
test('collapse adjacent rules', () => {
let config = {
purge: [path.resolve(__dirname, './collapse-adjacent-rules.test.html')],
corePlugins: { preflight: false },
theme: {},
plugins: [],
}
let 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");
}
}
`
return run(css, config).then((result) => {
let expectedPath = path.resolve(__dirname, './collapse-adjacent-rules.test.css')
let expected = fs.readFileSync(expectedPath, 'utf8')
expect(result.css).toMatchCss(expected)
})
})

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<link rel="stylesheet" href="./tailwind.css" />
</head>
<body></body>
</html>

View file

@ -0,0 +1,17 @@
const path = require('path')
const process = require('child_process')
const { promisify } = require('util')
const exec = promisify(process.exec)
test('a build re-uses the context across multiple files with the same config', async () => {
const filepath = path.resolve(__dirname, './context-reuse.worker.js')
const { stdout } = await exec(`node ${filepath}`)
const results = JSON.parse(stdout.trim())
expect(results[0].activeContexts).toBe(1)
expect(results[1].activeContexts).toBe(1)
expect(results[2].activeContexts).toBe(1)
expect(results[3].activeContexts).toBe(1)
})

View file

@ -0,0 +1,47 @@
const fs = require('fs')
const path = require('path')
const postcss = require('postcss')
const tailwind = require('../index.js')
const sharedState = require('../lib/sharedState.js')
const configPath = path.resolve(__dirname, './context-reuse.tailwind.config.js')
function run(input, config = {}, from = null) {
from = from || path.resolve(__filename)
return postcss([tailwind(config)]).process(input, { from })
}
async function runTest() {
let messages = []
let config = {
darkMode: 'class',
purge: [path.resolve(__dirname, './context-reuse.test.html')],
corePlugins: { preflight: false },
theme: {},
plugins: [],
}
let from = path.resolve(__filename)
fs.writeFileSync(configPath, `module.exports = ${JSON.stringify(config)};`)
let results = [
await run(`@tailwind utilities;`, configPath, `${from}?id=1`),
await run(`body { @apply bg-blue-400; }`, configPath, `${from}?id=2`),
await run(`body { @apply text-red-400; }`, configPath, `${from}?id=3`),
await run(`body { @apply mb-4; }`, configPath, `${from}?id=4`),
]
results.forEach(() => {
messages.push({
activeContexts: sharedState.contextSourcesMap.size,
})
})
process.stdout.write(JSON.stringify(messages))
}
runTest().finally(() => {
fs.unlinkSync(configPath)
})

View file

@ -0,0 +1,17 @@
* {
--tw-shadow: 0 0 #0000;
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgba(59, 130, 246, 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
}
.bg-white {
--tw-bg-opacity: 1;
background-color: rgba(255, 255, 255, var(--tw-bg-opacity));
}
.text-indigo-500 {
--tw-text-opacity: 1;
color: rgba(99, 102, 241, var(--tw-text-opacity));
}

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<link rel="stylesheet" href="./tailwind.css" />
</head>
<body>
<div class="text-indigo-500 bg-white">hello world</div>
<span>text-red-500 shouldn't appear in the output</span>
</body>
</html>

View file

@ -0,0 +1,63 @@
const postcss = require('postcss')
const fs = require('fs')
const path = require('path')
function run(input, config = {}) {
jest.resetModules()
const tailwind = require('../index.js')
return postcss([tailwind(config)]).process(input, { from: path.resolve(__filename) })
}
function customExtractor(content) {
const matches = content.match(/class="([^"]+)"/)
return matches ? matches[1].split(/\s+/) : []
}
const css = `
@tailwind base;
@tailwind components;
@tailwind utilities;
`
const expectedPath = path.resolve(__dirname, './custom-extractors.test.css')
const expected = fs.readFileSync(expectedPath, 'utf8')
test('defaultExtractor', () => {
let config = {
purge: {
content: [path.resolve(__dirname, './custom-extractors.test.html')],
options: {
defaultExtractor: customExtractor,
},
},
corePlugins: { preflight: false },
theme: {},
plugins: [],
}
return run(css, config).then((result) => {
expect(result.css).toMatchCss(expected)
})
})
test('extractors array', () => {
let config = {
purge: {
content: [path.resolve(__dirname, './custom-extractors.test.html')],
options: {
extractors: [
{
extractor: customExtractor,
extensions: ['html'],
},
],
},
},
corePlugins: { preflight: false },
theme: {},
plugins: [],
}
return run(css, config).then((result) => {
expect(result.css).toMatchCss(expected)
})
})

View file

@ -0,0 +1,19 @@
.group_hover .group-hover_focus-within_text-left:focus-within {
text-align: left;
}
[dir='rtl'] .rtl_active_text-center:active {
text-align: center;
}
@media (prefers-reduced-motion: no-preference) {
.motion-safe_hover_text-center:hover {
text-align: center;
}
}
.dark .dark_focus_text-left:focus {
text-align: left;
}
@media (min-width: 768px) {
.md_hover_text-right:hover {
text-align: right;
}
}

View file

@ -0,0 +1,5 @@
<div class="md_hover_text-right"></div>
<div class="motion-safe_hover_text-center"></div>
<div class="dark_focus_text-left"></div>
<div class="group-hover_focus-within_text-left"></div>
<div class="rtl_active_text-center"></div>

View file

@ -0,0 +1,28 @@
const postcss = require('postcss')
const tailwind = require('../index.js')
const fs = require('fs')
const path = require('path')
function run(input, config = {}) {
return postcss([tailwind(config)]).process(input, { from: path.resolve(__filename) })
}
test('custom separator', () => {
let config = {
darkMode: 'class',
purge: [path.resolve(__dirname, './custom-separator.test.html')],
separator: '_',
corePlugins: {},
theme: {},
plugins: [],
}
let css = `@tailwind utilities`
return run(css, config).then((result) => {
let expectedPath = path.resolve(__dirname, './custom-separator.test.css')
let expected = fs.readFileSync(expectedPath, 'utf8')
expect(result.css).toMatchCss(expected)
})
})

View file

@ -0,0 +1,52 @@
* {
--tw-shadow: 0 0 #0000;
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgba(59, 130, 246, 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
}
h1 {
font-size: 32px;
}
.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: rgba(0, 0, 0, var(--tw-bg-opacity));
}
@media (min-width: 768px) {
.md\:hover\:text-center:hover {
text-align: center;
}
}

View file

@ -0,0 +1,5 @@
<h1>Hello world!</h1>
<div class="container"></div>
<div class="mt-6"></div>
<div class="bg-black"></div>
<div class="md:hover:text-center"></div>

View file

@ -0,0 +1,38 @@
const postcss = require('postcss')
const tailwind = require('../index.js')
const fs = require('fs')
const path = require('path')
function run(input, config = {}) {
return postcss([tailwind(config)]).process(input, { from: path.resolve(__filename) })
}
test('using @import instead of @tailwind', () => {
let config = {
purge: [path.resolve(__dirname, './import-syntax.test.html')],
corePlugins: { preflight: false },
theme: {},
plugins: [
function ({ addBase }) {
addBase({
h1: {
fontSize: '32px',
},
})
},
],
}
let css = `
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
`
return run(css, config).then((result) => {
let expectedPath = path.resolve(__dirname, './import-syntax.test.css')
let expected = fs.readFileSync(expectedPath, 'utf8')
expect(result.css).toMatchCss(expected)
})
})

View file

@ -0,0 +1,85 @@
* {
--tw-shadow: 0 0 #0000;
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgba(59, 130, 246, 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
}
.container {
width: 100%;
}
@media (min-width: 640px) {
.container {
max-width: 640px;
}
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
}
@media (min-width: 1536px) {
.container {
max-width: 1536px;
}
}
.btn {
button: yes !important;
}
@font-face {
font-family: Inter;
}
@page {
margin: 1cm;
}
.custom-component {
font-weight: 700;
}
.custom-important-component {
text-align: center !important;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.animate-spin {
animation: spin 1s linear infinite !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;
}
[dir='rtl'] .rtl\:active\:text-center:active {
text-align: center !important;
}
@media (prefers-reduced-motion: no-preference) {
.motion-safe\:hover\:text-center:hover {
text-align: center !important;
}
}
.dark .dark\:focus\:text-left:focus {
text-align: left !important;
}
@media (min-width: 768px) {
.md\:hover\:text-right:hover {
text-align: right !important;
}
}

View file

@ -0,0 +1,12 @@
<div class="container"></div>
<div class="btn"></div>
<div class="animate-spin"></div>
<div class="custom-util"></div>
<div class="custom-component"></div>
<div class="custom-important-component"></div>
<div class="font-bold"></div>
<div class="md:hover:text-right"></div>
<div class="motion-safe:hover:text-center"></div>
<div class="dark:focus:text-left"></div>
<div class="group-hover:focus-within:text-left"></div>
<div class="rtl:active:text-center"></div>

View file

@ -0,0 +1,70 @@
const postcss = require('postcss')
const tailwind = require('../index.js')
const fs = require('fs')
const path = require('path')
function run(input, config = {}) {
return postcss([tailwind(config)]).process(input, { from: path.resolve(__filename) })
}
test('important boolean', () => {
let config = {
important: true,
darkMode: 'class',
purge: [path.resolve(__dirname, './important-boolean.test.html')],
corePlugins: { preflight: false },
theme: {},
plugins: [
function ({ addComponents, addUtilities }) {
addComponents(
{
'.btn': {
button: 'yes',
},
},
{ respectImportant: true }
)
addComponents(
{
'@font-face': {
'font-family': 'Inter',
},
'@page': {
margin: '1cm',
},
},
{ respectImportant: true }
)
addUtilities(
{
'.custom-util': {
button: 'no',
},
},
{ respectImportant: false }
)
},
],
}
let css = `
@tailwind base;
@tailwind components;
@layer components {
.custom-component {
@apply font-bold;
}
.custom-important-component {
@apply text-center !important;
}
}
@tailwind utilities;
`
return run(css, config).then((result) => {
let expectedPath = path.resolve(__dirname, './important-boolean.test.css')
let expected = fs.readFileSync(expectedPath, 'utf8')
expect(result.css).toMatchCss(expected)
})
})

View file

@ -0,0 +1,53 @@
* {
--tw-shadow: 0 0 #0000;
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgba(59, 130, 246, 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
}
.\!tw-container {
width: 100% !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: 1536px) {
.\!tw-container {
max-width: 1536px !important;
}
}
.\!tw-font-bold {
font-weight: 700 !important;
}
.hover\:\!tw-text-center:hover {
text-align: center !important;
}
@media (min-width: 1024px) {
.lg\:\!tw-opacity-50 {
opacity: 0.5 !important;
}
}
@media (min-width: 1280px) {
.xl\:focus\:disabled\:\!tw-float-right:focus:disabled {
float: right !important;
}
}

View file

@ -0,0 +1,5 @@
<div class="!tw-container"></div>
<div class="!tw-font-bold"></div>
<div class="hover:!tw-text-center"></div>
<div class="lg:!tw-opacity-50"></div>
<div class="xl:focus:disabled:!tw-float-right"></div>

View file

@ -0,0 +1,33 @@
const postcss = require('postcss')
const tailwind = require('../index.js')
const fs = require('fs')
const path = require('path')
function run(input, config = {}) {
return postcss([tailwind(config)]).process(input, { from: path.resolve(__filename) })
}
test('important modifier with prefix', () => {
let config = {
important: false,
prefix: 'tw-',
darkMode: 'class',
purge: [path.resolve(__dirname, './important-modifier-prefix.test.html')],
corePlugins: { preflight: false },
theme: {},
plugins: [],
}
let css = `
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(css, config).then((result) => {
let expectedPath = path.resolve(__dirname, './important-modifier-prefix.test.css')
let expected = fs.readFileSync(expectedPath, 'utf8')
expect(result.css).toMatchCss(expected)
})
})

View file

@ -0,0 +1,53 @@
* {
--tw-shadow: 0 0 #0000;
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgba(59, 130, 246, 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
}
.\!container {
width: 100% !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;
}
}
.\!font-bold {
font-weight: 700 !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:focus:disabled {
float: right !important;
}
}

View file

@ -0,0 +1,5 @@
<div class="!container"></div>
<div class="!font-bold"></div>
<div class="hover:!text-center"></div>
<div class="lg:!opacity-50"></div>
<div class="xl:focus:disabled:!float-right"></div>

View file

@ -0,0 +1,32 @@
const postcss = require('postcss')
const tailwind = require('../index.js')
const fs = require('fs')
const path = require('path')
function run(input, config = {}) {
return postcss([tailwind(config)]).process(input, { from: path.resolve(__filename) })
}
test('important modifier', () => {
let config = {
important: false,
darkMode: 'class',
purge: [path.resolve(__dirname, './important-modifier.test.html')],
corePlugins: { preflight: false },
theme: {},
plugins: [],
}
let css = `
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(css, config).then((result) => {
let expectedPath = path.resolve(__dirname, './important-modifier.test.css')
let expected = fs.readFileSync(expectedPath, 'utf8')
expect(result.css).toMatchCss(expected)
})
})

View file

@ -0,0 +1,85 @@
* {
--tw-shadow: 0 0 #0000;
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgba(59, 130, 246, 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
}
.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;
}
@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 .animate-spin {
animation: spin 1s linear infinite;
}
#app .font-bold {
font-weight: 700;
}
.custom-util {
button: no;
}
#app .group:hover .group-hover\:focus-within\:text-left:focus-within {
text-align: left;
}
#app [dir='rtl'] .rtl\:active\:text-center:active {
text-align: center;
}
@media (prefers-reduced-motion: no-preference) {
#app .motion-safe\:hover\:text-center:hover {
text-align: center;
}
}
#app .dark .dark\:focus\:text-left:focus {
text-align: left;
}
@media (min-width: 768px) {
#app .md\:hover\:text-right:hover {
text-align: right;
}
}

View file

@ -0,0 +1,12 @@
<div class="container"></div>
<div class="btn"></div>
<div class="animate-spin"></div>
<div class="custom-util"></div>
<div class="custom-component"></div>
<div class="custom-important-component"></div>
<div class="font-bold"></div>
<div class="md:hover:text-right"></div>
<div class="motion-safe:hover:text-center"></div>
<div class="dark:focus:text-left"></div>
<div class="group-hover:focus-within:text-left"></div>
<div class="rtl:active:text-center"></div>

View file

@ -0,0 +1,70 @@
const postcss = require('postcss')
const tailwind = require('../index.js')
const fs = require('fs')
const path = require('path')
function run(input, config = {}) {
return postcss([tailwind(config)]).process(input, { from: path.resolve(__filename) })
}
test('important selector', () => {
let config = {
important: '#app',
darkMode: 'class',
purge: [path.resolve(__dirname, './important-selector.test.html')],
corePlugins: { preflight: false },
theme: {},
plugins: [
function ({ addComponents, addUtilities }) {
addComponents(
{
'.btn': {
button: 'yes',
},
},
{ respectImportant: true }
)
addComponents(
{
'@font-face': {
'font-family': 'Inter',
},
'@page': {
margin: '1cm',
},
},
{ respectImportant: true }
)
addUtilities(
{
'.custom-util': {
button: 'no',
},
},
{ respectImportant: false }
)
},
],
}
let css = `
@tailwind base;
@tailwind components;
@layer components {
.custom-component {
@apply font-bold;
}
.custom-important-component {
@apply text-center !important;
}
}
@tailwind utilities;
`
return run(css, config).then((result) => {
let expectedPath = path.resolve(__dirname, './important-selector.test.css')
let expected = fs.readFileSync(expectedPath, 'utf8')
expect(result.css).toMatchCss(expected)
})
})

View file

@ -0,0 +1,568 @@
.theme-test {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
color: #3b82f6;
}
@media (min-width: 1024px) {
.screen-test {
color: purple;
}
}
.apply-1 {
margin-top: 1.5rem;
}
.apply-2 {
margin-top: 1.5rem;
}
.apply-test {
margin-top: 1.5rem;
--tw-bg-opacity: 1;
background-color: rgba(236, 72, 153, var(--tw-bg-opacity));
}
.apply-test:hover {
font-weight: 700;
}
.apply-test:focus:hover {
font-weight: 700;
}
@media (min-width: 640px) {
.apply-test {
--tw-bg-opacity: 1;
background-color: rgba(16, 185, 129, var(--tw-bg-opacity));
}
.apply-test:focus:nth-child(even) {
--tw-bg-opacity: 1;
background-color: rgba(251, 207, 232, var(--tw-bg-opacity));
}
}
.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 {
font-weight: 700;
}
.group:hover .apply-group {
font-weight: 700;
}
.dark .apply-dark-mode {
font-weight: 700;
}
.apply-with-existing:hover {
font-weight: 400;
}
@media (min-width: 640px) {
.apply-with-existing:hover {
--tw-bg-opacity: 1;
background-color: rgba(16, 185, 129, 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-x-reverse: 0;
margin-right: calc(1rem * var(--tw-space-x-reverse));
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
}
.nested {
.example {
font-weight: 700;
}
.example:hover {
font-weight: 400;
}
}
.apply-order-a {
margin: 1.25rem;
margin-top: 1.5rem;
}
.apply-order-b {
margin: 1.25rem;
margin-top: 1.5rem;
}
.dark .group:hover .apply-dark-group-example-a {
--tw-bg-opacity: 1;
background-color: rgba(16, 185, 129, var(--tw-bg-opacity));
}
@media (min-width: 640px) {
@media (prefers-reduced-motion: no-preference) {
.group:active .crazy-example:focus {
opacity: 0.1;
}
}
}
* {
--tw-shadow: 0 0 #0000;
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgba(59, 130, 246, 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
}
h1 {
font-size: 1.5rem;
font-weight: 700;
}
h1:first-child {
margin-top: 0px;
}
div {
background: #654321;
}
.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;
}
}
.aspect-w-1,
.aspect-w-2,
.aspect-w-3,
.aspect-w-4,
.aspect-w-5,
.aspect-w-6,
.aspect-w-7,
.aspect-w-8,
.aspect-w-9,
.aspect-w-10,
.aspect-w-11,
.aspect-w-12,
.aspect-w-13,
.aspect-w-14,
.aspect-w-15,
.aspect-w-16 {
position: relative;
padding-bottom: calc(var(--tw-aspect-h) / var(--tw-aspect-w) * 100%);
}
.aspect-w-1 > *,
.aspect-w-2 > *,
.aspect-w-3 > *,
.aspect-w-4 > *,
.aspect-w-5 > *,
.aspect-w-6 > *,
.aspect-w-7 > *,
.aspect-w-8 > *,
.aspect-w-9 > *,
.aspect-w-10 > *,
.aspect-w-11 > *,
.aspect-w-12 > *,
.aspect-w-13 > *,
.aspect-w-14 > *,
.aspect-w-15 > *,
.aspect-w-16 > * {
position: absolute;
height: 100%;
width: 100%;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.aspect-w-1 {
--tw-aspect-w: 1;
}
.aspect-w-3 {
--tw-aspect-w: 3;
}
.aspect-h-2 {
--tw-aspect-h: 2;
}
.aspect-h-4 {
--tw-aspect-h: 4;
}
.test-apply-font-variant {
--tw-ordinal: var(--tw-empty, /*!*/ /*!*/);
--tw-slashed-zero: var(--tw-empty, /*!*/ /*!*/);
--tw-numeric-figure: var(--tw-empty, /*!*/ /*!*/);
--tw-numeric-spacing: var(--tw-empty, /*!*/ /*!*/);
--tw-numeric-fraction: var(--tw-empty, /*!*/ /*!*/);
font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
var(--tw-numeric-spacing) var(--tw-numeric-fraction);
--tw-ordinal: var(--tw-empty, /*!*/ /*!*/);
--tw-slashed-zero: var(--tw-empty, /*!*/ /*!*/);
--tw-numeric-figure: var(--tw-empty, /*!*/ /*!*/);
--tw-numeric-spacing: var(--tw-empty, /*!*/ /*!*/);
--tw-numeric-fraction: var(--tw-empty, /*!*/ /*!*/);
font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
var(--tw-numeric-spacing) var(--tw-numeric-fraction);
--tw-ordinal: ordinal;
--tw-numeric-spacing: tabular-nums;
}
.custom-component {
background: #123456;
}
* {
padding: 5px;
}
.foo .bg-black {
appearance: none;
}
.mt-6 {
margin-top: 1.5rem;
}
.transform {
--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;
transform: translateX(var(--tw-translate-x)) translateY(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-50 {
--tw-scale-x: 0.5;
--tw-scale-y: 0.5;
}
.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;
}
.bg-black {
--tw-bg-opacity: 1;
background-color: rgba(0, 0, 0, var(--tw-bg-opacity));
}
.bg-green-500 {
--tw-bg-opacity: 1;
background-color: rgba(16, 185, 129, 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;
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgba(186, 218, 85, 0));
}
.text-center {
text-align: center;
}
.font-medium {
font-weight: 500;
}
.shadow-sm {
--tw-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.filter-none {
filter: none;
}
.filter-grayscale {
filter: grayscale(100%);
}
.custom-util {
background: #abcdef;
}
* {
margin: 10px;
}
.first\:pt-0:first-child {
padding-top: 0px;
}
.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;
}
}
.hover\:scale-75:hover {
--tw-scale-x: 0.75;
--tw-scale-y: 0.75;
}
.hover\:font-bold:hover {
font-weight: 700;
}
.hover\:shadow-lg:hover {
--tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
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: rgba(59, 130, 246, var(--tw-ring-opacity));
}
.focus\:hover\:font-light:focus:hover {
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 {
background: #abcdef !important;
}
.foo\:hover\:custom-util:hover {
background: #abcdef !important;
}
@media (prefers-reduced-motion: no-preference) {
.motion-safe\:transition {
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow,
transform;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.motion-safe\:custom-util {
background: #abcdef;
}
}
@media (prefers-reduced-motion: reduce) {
.motion-reduce\:transition {
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow,
transform;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
}
.dark .dark\:custom-util {
background: #abcdef;
}
@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;
}
}
.sm\:text-center {
text-align: center;
}
.sm\:ordinal,
.sm\:slashed-zero,
.sm\:lining-nums,
.sm\:oldstyle-nums,
.sm\:proportional-nums,
.sm\:tabular-nums,
.sm\:diagonal-fractions,
.sm\:stacked-fractions {
--tw-ordinal: var(--tw-empty, /*!*/ /*!*/);
--tw-slashed-zero: var(--tw-empty, /*!*/ /*!*/);
--tw-numeric-figure: var(--tw-empty, /*!*/ /*!*/);
--tw-numeric-spacing: var(--tw-empty, /*!*/ /*!*/);
--tw-numeric-fraction: var(--tw-empty, /*!*/ /*!*/);
font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
var(--tw-numeric-spacing) var(--tw-numeric-fraction);
}
.sm\:tabular-nums {
--tw-numeric-spacing: tabular-nums;
}
.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\: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;
}
}
.md\:text-center {
text-align: center;
}
.md\:opacity-50 {
opacity: 0.5;
}
.md\:shadow-sm {
--tw-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.md\:hover\:opacity-20:hover {
opacity: 0.2;
}
@media (prefers-reduced-motion: no-preference) {
.md\:motion-safe\:hover\:transition:hover {
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow,
transform;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
}
@media (prefers-reduced-motion: no-preference) {
.dark .md\:dark\:motion-safe\:foo\:active\:custom-util:active {
background: #abcdef !important;
}
}
@media (min-width: 640px) {
.md\:sm\:text-center {
text-align: center;
}
}
}
@media (orientation: portrait) {
.portrait\: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;
}
}

View file

@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<link rel="stylesheet" href="./tailwind.css" />
</head>
<body>
<div class="portrait:text-center range:text-right multi:text-left"></div>
<div
class="container hover:container sm:container md:container text-center sm:text-center md:text-center"
></div>
<div class="grid-cols-[200px,repeat(auto-fill,minmax(15%,100px)),300px]"></div>
<div class="test-apply-font-variant"></div>
<div class="mt-6"></div>
<div class="bg-black"></div>
<div class="custom-util"></div>
<div class="hover:custom-util"></div>
<div class="group-hover:custom-util"></div>
<div class="foo:custom-util"></div>
<div class="foo:hover:custom-util"></div>
<div class="sm:custom-util"></div>
<div class="dark:custom-util"></div>
<div class="motion-safe:custom-util"></div>
<div class="md:dark:motion-safe:foo:active:custom-util"></div>
<div class="aspect-w-1 aspect-h-2"></div>
<div class="aspect-w-3 aspect-h-4"></div>
<div class="filter-none filter-grayscale"></div>
<div class="focus:font-normal"></div>
<div class="font-medium"></div>
<div class="bg-gradient-to-r from-foo"></div>
<div class="custom-component custom-util"></div>
<div class="bg-opacity-50"></div>
<div class="focus:ring-2 focus:ring-blue-500"></div>
<div class="hover:font-bold"></div>
<div class="disabled:font-bold"></div>
<div class="focus:hover:font-light"></div>
<div class="first:pt-0"></div>
<div class="container"></div>
<div class="bg-hero--home-1"></div>
<div class="group-hover:opacity-100"></div>
<div class="group-active:opacity-10"></div>
<div class="sm:motion-safe:group-active:focus:opacity-10"></div>
<div class="motion-safe:transition"></div>
<div class="motion-reduce:transition"></div>
<div class="md:motion-safe:hover:transition"></div>
<div class="md:sm:text-center shadow-sm md:shadow-sm"></div>
<div class="md:sm:text-center shadow-sm md:shadow-sm"></div>
<div class="bg-green-500 md:opacity-50 md:hover:opacity-20 sm:tabular-nums"></div>
<div class="text-center shadow-md hover:shadow-lg transform scale-50 hover:scale-75"></div>
</body>
</html>
<script>
defineComponent({
name: 'HelloWorld',
props: {
msg: {
type: String,
required: true,
},
things: Array /* PropType<string[]> */,
},
setup: () => {
const count = ref(0)
// Weird regex-looking stuff that once caused a stack overflow in candidatePermutations
const pattern = ' ]-[] '
return {
count,
stuff: [] /* string[] | undefined */,
}
},
})
</script>

View file

@ -0,0 +1,166 @@
const postcss = require('postcss')
const tailwind = require('../index.js')
const fs = require('fs')
const path = require('path')
function run(input, config = {}) {
return postcss([tailwind(config)]).process(input, { from: path.resolve(__filename) })
}
test('it works', () => {
let config = {
darkMode: 'class',
purge: [path.resolve(__dirname, './kitchen-sink.test.html')],
corePlugins: { preflight: false },
theme: {
extend: {
screens: {
portrait: { raw: '(orientation: portrait)' },
range: { min: '1280px', max: '1535px' },
multi: [{ min: '640px', max: '767px' }, { max: '868px' }],
},
gradientColorStops: {
foo: '#bada55',
},
backgroundImage: {
'hero--home-1': "url('/images/homepage-1.jpg')",
},
},
},
plugins: [
require('@tailwindcss/aspect-ratio'),
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',
},
},
})
addUtilities(
{
'.filter-none': {
filter: 'none',
},
'.filter-grayscale': {
filter: 'grayscale(100%)',
},
},
['responsive', 'hover']
)
},
],
}
let css = `
@layer utilities {
.custom-util {
background: #abcdef;
}
* {
margin: 10px;
}
}
@layer components {
.test-apply-font-variant {
@apply ordinal tabular-nums;
}
.custom-component {
background: #123456;
}
* {
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-x-4;
}
.nested {
.example {
@apply font-bold hover:font-normal;
}
}
.apply-order-a {
@apply m-5 mt-6;
}
.apply-order-b {
@apply mt-6 m-5;
}
.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(css, config).then((result) => {
let expectedPath = path.resolve(__dirname, './kitchen-sink.test.css')
let expected = fs.readFileSync(expectedPath, 'utf8')
expect(result.css).toMatchCss(expected)
})
})

View file

@ -0,0 +1,33 @@
.markdown > p {
margin-top: 12px;
}
.font-bold {
font-weight: 700;
}
.foo .foo\:markdown > p {
margin-top: 12px;
}
.foo .foo\:font-bold {
font-weight: 700;
}
.foo .foo\:visited\:markdown:visited > p {
margin-top: 12px;
}
.foo .foo\:hover\:font-bold:hover {
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;
}
}
@media (min-width: 1024px) {
.foo .lg\:foo\:disabled\:markdown:disabled > p {
margin-top: 12px;
}
}

View file

@ -0,0 +1,17 @@
<div class="font-bold"></div>
<div class="foo:font-bold"></div>
<div class="foo:hover:font-bold"></div>
<div class="sm:foo:font-bold"></div>
<div class="md:foo:focus:font-bold"></div>
<div class="markdown">
<p>Lorem ipsum dolor sit amet...</p>
</div>
<div class="foo:markdown">
<p>Lorem ipsum dolor sit amet...</p>
</div>
<div class="foo:visited:markdown">
<p>Lorem ipsum dolor sit amet...</p>
</div>
<div class="lg:foo:disabled:markdown">
<p>Lorem ipsum dolor sit amet...</p>
</div>

View file

@ -0,0 +1,50 @@
const postcss = require('postcss')
const tailwind = require('../index.js')
const fs = require('fs')
const path = require('path')
const selectorParser = require('postcss-selector-parser')
function run(input, config = {}) {
return postcss([tailwind(config)]).process(input, { from: path.resolve(__filename) })
}
test('modify selectors', () => {
let config = {
darkMode: 'class',
purge: [path.resolve(__dirname, './modify-selectors.test.html')],
corePlugins: { preflight: false },
theme: {},
plugins: [
function ({ addVariant, e }) {
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 css = `
@tailwind components;
@tailwind utilities;
@layer components {
.markdown > p {
margin-top: 12px;
}
}
`
return run(css, config).then((result) => {
let expectedPath = path.resolve(__dirname, './modify-selectors.test.css')
let expected = fs.readFileSync(expectedPath, 'utf8')
expect(result.css).toMatchCss(expected)
})
})

View file

@ -0,0 +1,3 @@
.bg-foo {
background-image: url("./foo.png");
}

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<link rel="stylesheet" href="./tailwind.css" />
</head>
<body>
<div class="bg-foo"></div>
</body>
</html>

55
jit/tests/mutable.test.js Normal file
View file

@ -0,0 +1,55 @@
const postcss = require('postcss')
const tailwind = require('../index.js')
const fs = require('fs')
const path = require('path')
function pluginThatMutatesRules() {
return (root) => {
root.walkRules((rule) => {
rule.nodes
.filter((node) => node.prop === 'background-image')
.forEach((node) => {
node.value = 'url("./bar.png")'
})
return rule
})
}
}
function run(input, config = {}) {
return postcss([tailwind(config)]).process(input, { from: path.resolve(__filename) })
}
test.only('plugins mutating rules after tailwind doesnt break it', async () => {
let config = {
purge: [path.resolve(__dirname, './mutable.test.html')],
theme: {
backgroundImage: {
foo: 'url("./foo.png")',
},
},
plugins: [],
}
let css = `@tailwind utilities;`
function checkResult(result) {
let expectedPath = path.resolve(__dirname, './mutable.test.css')
let expected = fs.readFileSync(expectedPath, 'utf8')
expect(result.css).toMatchCss(expected)
}
// Verify the first run produces the expected result
let firstRun = await run(css, 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(css, config)
checkResult(secondRun)
})

View file

@ -0,0 +1,9 @@
.tw-ml-1 {
margin-left: 0.25rem;
}
.flex {
display: flex;
}
.tw-align-bottom {
vertical-align: bottom
}

View file

@ -0,0 +1,3 @@
<div class="flex"></div>
<div class="tw-align-bottom"></div>
<div class="tw-ml-1"></div>

View file

@ -0,0 +1,34 @@
const postcss = require('postcss')
const tailwind = require('../index.js')
const fs = require('fs')
const path = require('path')
function run(input, config = {}) {
return postcss([tailwind(config)]).process(input, { from: path.resolve(__filename) })
}
test('prefix fn', () => {
let config = {
prefix(selector) {
if (['.align-bottom', '.ml'].some((prefix) => selector.startsWith(prefix))) {
return 'tw-'
}
return ''
},
purge: [path.resolve(__dirname, './prefix.fn.test.html')],
corePlugins: { preflight: false },
theme: {},
}
let css = `
@tailwind utilities;
`
return run(css, config).then((result) => {
let expectedPath = path.resolve(__dirname, './prefix.fn.test.css')
let expected = fs.readFileSync(expectedPath, 'utf8')
expect(result.css).toMatchCss(expected)
})
})

86
jit/tests/prefix.test.css Normal file
View file

@ -0,0 +1,86 @@
* {
--tw-shadow: 0 0 #0000;
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgba(59, 130, 246, 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
}
.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 {
button: yes;
}
.btn-no-prefix {
button: yes;
}
.custom-component {
font-weight: 700;
}
.tw-dark .tw-group:hover .custom-component {
font-weight: 400;
}
.tw--ml-4 {
margin-left: -1rem;
}
.tw-font-bold {
font-weight: 700;
}
.tw-custom-util-prefix {
button: no;
}
.custom-util-no-prefix {
button: no;
}
.tw-group:hover .group-hover\:focus-within\:tw-text-left:focus-within {
text-align: left;
}
[dir='rtl'] .rtl\:active\:tw-text-center:active {
text-align: center;
}
@media (prefers-reduced-motion: no-preference) {
.motion-safe\:hover\:tw-text-center:hover {
text-align: center;
}
}
.tw-dark .dark\:focus\:tw-text-left:focus {
text-align: left;
}
@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;
}
}

View file

@ -0,0 +1,17 @@
<div class="tw--ml-4"></div>
<div class="md:tw--ml-5"></div>
<div class="md:hover:tw--ml-6"></div>
<div class="tw-container"></div>
<div class="btn-no-prefix"></div>
<div class="tw-btn-prefix"></div>
<div class="tw-custom-util-prefix"></div>
<div class="custom-util-no-prefix"></div>
<div class="custom-component"></div>
<div class="tw-custom-component-prefix"></div>
<div class="custom-component-no-prefix"></div>
<div class="tw-font-bold"></div>
<div class="md:hover:tw-text-right"></div>
<div class="motion-safe:hover:tw-text-center"></div>
<div class="dark:focus:tw-text-left"></div>
<div class="group-hover:focus-within:tw-text-left"></div>
<div class="rtl:active:tw-text-center"></div>

71
jit/tests/prefix.test.js Normal file
View file

@ -0,0 +1,71 @@
const postcss = require('postcss')
const tailwind = require('../index.js')
const fs = require('fs')
const path = require('path')
function run(input, config = {}) {
return postcss([tailwind(config)]).process(input, { from: path.resolve(__filename) })
}
test('prefix', () => {
let config = {
prefix: 'tw-',
darkMode: 'class',
purge: [path.resolve(__dirname, './prefix.test.html')],
corePlugins: { preflight: false },
theme: {},
plugins: [
function ({ addComponents, addUtilities }) {
addComponents({
'.btn-prefix': {
button: 'yes',
},
})
addComponents(
{
'.btn-no-prefix': {
button: 'yes',
},
},
{ respectPrefix: false }
)
addUtilities({
'.custom-util-prefix': {
button: 'no',
},
})
addUtilities(
{
'.custom-util-no-prefix': {
button: 'no',
},
},
{ respectPrefix: false }
)
},
],
}
let css = `
@tailwind base;
@tailwind components;
@layer components {
.custom-component {
@apply tw-font-bold dark:group-hover:tw-font-normal;
}
}
@tailwind utilities;
@layer utilites {
.custom-utility {
foo: bar;
}
}
`
return run(css, config).then((result) => {
let expectedPath = path.resolve(__dirname, './prefix.test.css')
let expected = fs.readFileSync(expectedPath, 'utf8')
expect(result.css).toMatchCss(expected)
})
})

View file

@ -0,0 +1,65 @@
* {
--tw-shadow: 0 0 #0000;
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgba(59, 130, 246, 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
}
.responsive-in-components {
color: blue;
}
.variants-in-components {
color: red;
}
.both-in-components {
color: green;
}
.responsive-in-utilities {
color: blue;
}
.variants-in-utilities {
color: red;
}
.both-in-utilities {
color: green;
}
.responsive-at-root {
color: white;
}
.variants-at-root {
color: orange;
}
.both-at-root {
color: pink;
}
@media (min-width: 768px) {
.md\:focus\:responsive-in-components:focus {
color: blue;
}
.md\:focus\:variants-in-components:focus {
color: red;
}
.md\:focus\:both-in-components:focus {
color: green;
}
.md\:focus\:responsive-in-utilities:focus {
color: blue;
}
.md\:focus\:variants-in-utilities:focus {
color: red;
}
.md\:focus\:both-in-utilities:focus {
color: green;
}
.md\:focus\:responsive-at-root:focus {
color: white;
}
.md\:focus\:variants-at-root:focus {
color: orange;
}
.md\:focus\:both-at-root:focus {
color: pink;
}
}

View file

@ -0,0 +1,18 @@
<div class="responsive-in-utilities"></div>
<div class="variants-in-utilities"></div>
<div class="both-in-utilities"></div>
<div class="responsive-at-root"></div>
<div class="variants-at-root"></div>
<div class="both-at-root"></div>
<div class="responsive-in-components"></div>
<div class="variants-in-components"></div>
<div class="both-in-components"></div>
<div class="md:focus:responsive-in-utilities"></div>
<div class="md:focus:variants-in-utilities"></div>
<div class="md:focus:both-in-utilities"></div>
<div class="md:focus:responsive-at-root"></div>
<div class="md:focus:variants-at-root"></div>
<div class="md:focus:both-at-root"></div>
<div class="md:focus:responsive-in-components"></div>
<div class="md:focus:variants-in-components"></div>
<div class="md:focus:both-in-components"></div>

View file

@ -0,0 +1,88 @@
const postcss = require('postcss')
const tailwind = require('../index.js')
const fs = require('fs')
const path = require('path')
function run(input, config = {}) {
return postcss([tailwind(config)]).process(input, { from: path.resolve(__filename) })
}
test('responsive and variants atrules', () => {
let config = {
purge: [path.resolve(__dirname, './responsive-and-variants-atrules.test.html')],
corePlugins: { preflight: false },
theme: {},
plugins: [],
}
let css = `
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer utilities {
@responsive {
.responsive-in-utilities {
color: blue;
}
}
@variants {
.variants-in-utilities {
color: red;
}
}
@responsive {
@variants {
.both-in-utilities {
color: green;
}
}
}
}
@responsive {
.responsive-at-root {
color: white;
}
}
@variants {
.variants-at-root {
color: orange;
}
}
@responsive {
@variants {
.both-at-root {
color: pink;
}
}
}
@layer components {
@responsive {
.responsive-in-components {
color: blue;
}
}
@variants {
.variants-in-components {
color: red;
}
}
@responsive {
@variants {
.both-in-components {
color: green;
}
}
}
}
`
return run(css, config).then((result) => {
let expectedPath = path.resolve(__dirname, './responsive-and-variants-atrules.test.css')
let expected = fs.readFileSync(expectedPath, 'utf8')
expect(result.css).toMatchCss(expected)
})
})

View file

@ -0,0 +1,15 @@
* {
--tw-shadow: 0 0 #0000;
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgba(59, 130, 246, 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
}
@media (min-width: 1024px) {
.lg\:hover\:bg-blue-500:hover {
--tw-bg-opacity: 1;
background-color: rgba(59, 130, 246, var(--tw-bg-opacity));
}
}

View file

@ -0,0 +1,30 @@
const postcss = require('postcss')
const tailwind = require('../index.js')
const fs = require('fs')
const path = require('path')
function run(input, config = {}) {
return postcss([tailwind(config)]).process(input, { from: path.resolve(__filename) })
}
test('basic usage', () => {
let config = {
purge: [path.resolve(__dirname, './svelte-syntax.test.svelte')],
corePlugins: { preflight: false },
theme: {},
plugins: [],
}
let css = `
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(css, config).then((result) => {
let expectedPath = path.resolve(__dirname, './svelte-syntax.test.css')
let expected = fs.readFileSync(expectedPath, 'utf8')
expect(result.css).toMatchCss(expected)
})
})

View file

@ -0,0 +1,5 @@
<script>
let current = 'foo'
</script>
<button class:lg:hover:bg-blue-500={current === 'foo'}>Click me</button>

225
jit/tests/variants.test.css Normal file
View file

@ -0,0 +1,225 @@
* {
--tw-shadow: 0 0 #0000;
--tw-ring-inset: var(--tw-empty, /*!*/ /*!*/);
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgba(59, 130, 246, 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
}
.shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.first\:shadow-md:first-child {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.last\:shadow-md:last-child {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.odd\:shadow-md:nth-child(odd) {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.even\:shadow-md:nth-child(even) {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.visited\:shadow-md:visited {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.checked\:shadow-md:checked {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.focus-within\:shadow-md:focus-within {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.hover\:shadow-md:hover {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.focus\:shadow-md:focus {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.focus\:hover\:shadow-md:focus:hover {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.focus-visible\:shadow-md:focus-visible {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.active\:shadow-md:active {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.disabled\:shadow-md:disabled {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.group:first-child .group-first\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.group:last-child .group-last\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.group:nth-child(odd) .group-odd\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.group:nth-child(even) .group-even\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.group:visited .group-visited\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.group:checked .group-checked\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.group:focus-within .group-focus-within\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.group:hover .group-hover\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.group:focus .group-focus\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.group:focus-visible .group-focus-visible\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.group:active .group-active\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.group:disabled .group-disabled\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
[dir='ltr'] .ltr\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
[dir='rtl'] .rtl\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
@media (prefers-reduced-motion: no-preference) {
.motion-safe\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
}
@media (prefers-reduced-motion: reduce) {
.motion-reduce\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
}
.dark .dark\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
@media (min-width: 640px) {
.sm\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.sm\:active\:shadow-md:active {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
}
@media (min-width: 768px) {
.md\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.group:focus .md\:group-focus\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
}
@media (min-width: 1024px) {
.lg\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
.dark .lg\:dark\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
}
@media (min-width: 1280px) {
.xl\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
}
@media (min-width: 1536px) {
.\32xl\:shadow-md {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
@media (prefers-reduced-motion: no-preference) {
.dark .\32xl\:dark\:motion-safe\:focus-within\:shadow-md:focus-within {
--tw-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
}
}

View file

@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
<link rel="stylesheet" href="./tailwind.css" />
</head>
<body>
<!-- Basic pseudo variants -->
<div class="shadow-md"></div>
<div class="hover:shadow-md"></div>
<div class="focus:shadow-md"></div>
<div class="disabled:shadow-md"></div>
<div class="active:shadow-md"></div>
<div class="first:shadow-md"></div>
<div class="last:shadow-md"></div>
<div class="even:shadow-md"></div>
<div class="odd:shadow-md"></div>
<div class="visited:shadow-md"></div>
<div class="checked:shadow-md"></div>
<div class="focus-within:shadow-md"></div>
<div class="focus-visible:shadow-md"></div>
<!-- Group variants -->
<div class="group-hover:shadow-md"></div>
<div class="group-focus:shadow-md"></div>
<div class="group-disabled:shadow-md"></div>
<div class="group-active:shadow-md"></div>
<div class="group-first:shadow-md"></div>
<div class="group-last:shadow-md"></div>
<div class="group-even:shadow-md"></div>
<div class="group-odd:shadow-md"></div>
<div class="group-odd:shadow-md"></div>
<div class="group-visited:shadow-md"></div>
<div class="group-checked:shadow-md"></div>
<div class="group-focus-within:shadow-md"></div>
<div class="group-focus-visible:shadow-md"></div>
<div class="group-focus-visible:shadow-md"></div>
<!-- Reduced motion variants -->
<div class="motion-safe:shadow-md"></div>
<div class="motion-reduce:shadow-md"></div>
<!-- Direction variants -->
<div class="rtl:shadow-md"></div>
<div class="ltr:shadow-md"></div>
<!-- Dark mode variants -->
<div class="dark:shadow-md"></div>
<!-- Screen variants -->
<div class="sm:shadow-md"></div>
<div class="md:shadow-md"></div>
<div class="lg:shadow-md"></div>
<div class="xl:shadow-md"></div>
<div class="2xl:shadow-md"></div>
<!-- Stacked variants -->
<div class="focus:hover:shadow-md"></div>
<div class="sm:active:shadow-md"></div>
<div class="md:group-focus:shadow-md"></div>
<div class="lg:dark:shadow-md"></div>
<div class="xl:dark:disabledshadow-md"></div>
<div class="2xl:dark:motion-safe:focus-within:shadow-md"></div>
</body>
</html>

View file

@ -0,0 +1,31 @@
const postcss = require('postcss')
const tailwind = require('../index.js')
const fs = require('fs')
const path = require('path')
function run(input, config = {}) {
return postcss([tailwind(config)]).process(input, { from: path.resolve(__filename) })
}
test('variants', () => {
let config = {
darkMode: 'class',
purge: [path.resolve(__dirname, './variants.test.html')],
corePlugins: { preflight: false },
theme: {},
plugins: [],
}
let css = `
@tailwind base;
@tailwind components;
@tailwind utilities;
`
return run(css, config).then((result) => {
let expectedPath = path.resolve(__dirname, './variants.test.css')
let expected = fs.readFileSync(expectedPath, 'utf8')
expect(result.css).toMatchCss(expected)
})
})