mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
This has already been fixed due to another change but wanted to add an explicit test for it
1256 lines
25 KiB
JavaScript
1256 lines
25 KiB
JavaScript
import fs from 'fs'
|
|
import path from 'path'
|
|
|
|
import { run, html, css, defaults } from './util/run'
|
|
|
|
test('@apply', () => {
|
|
let config = {
|
|
darkMode: 'class',
|
|
content: [path.resolve(__dirname, './apply.test.html')],
|
|
}
|
|
|
|
let input = css`
|
|
@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;
|
|
}
|
|
.complex-utilities {
|
|
@apply ordinal tabular-nums focus:diagonal-fractions shadow-lg hover:shadow-xl;
|
|
}
|
|
.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;
|
|
}
|
|
|
|
.important-modifier {
|
|
@apply px-4 !rounded-md;
|
|
}
|
|
|
|
.important-modifier-variant {
|
|
@apply px-4 hover:!rounded-md;
|
|
}
|
|
}
|
|
|
|
@layer utilities {
|
|
.custom-util {
|
|
custom: stuff;
|
|
}
|
|
|
|
.foo {
|
|
@apply animate-spin;
|
|
}
|
|
|
|
.bar {
|
|
@apply animate-pulse !important;
|
|
}
|
|
}
|
|
`
|
|
|
|
return run(input, config).then((result) => {
|
|
let expectedPath = path.resolve(__dirname, './apply.test.css')
|
|
let expected = fs.readFileSync(expectedPath, 'utf8')
|
|
|
|
expect(result.css).toMatchFormattedCss(expected)
|
|
})
|
|
})
|
|
|
|
test('@apply error with unknown utility', async () => {
|
|
let config = {
|
|
darkMode: 'class',
|
|
content: [path.resolve(__dirname, './apply.test.html')],
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
@layer components {
|
|
.foo {
|
|
@apply a-utility-that-does-not-exist;
|
|
}
|
|
}
|
|
`
|
|
|
|
await expect(run(input, config)).rejects.toThrowError('class does not exist')
|
|
})
|
|
|
|
test('@apply error with nested @screen', async () => {
|
|
let config = {
|
|
darkMode: 'class',
|
|
content: [path.resolve(__dirname, './apply.test.html')],
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
@layer components {
|
|
.foo {
|
|
@screen md {
|
|
@apply text-black;
|
|
}
|
|
}
|
|
}
|
|
`
|
|
|
|
await expect(run(input, config)).rejects.toThrowError(
|
|
'@apply is not supported within nested at-rules like @screen'
|
|
)
|
|
})
|
|
|
|
test('@apply error with nested @anyatrulehere', async () => {
|
|
let config = {
|
|
darkMode: 'class',
|
|
content: [path.resolve(__dirname, './apply.test.html')],
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
@layer components {
|
|
.foo {
|
|
@genie {
|
|
@apply text-black;
|
|
}
|
|
}
|
|
}
|
|
`
|
|
|
|
await expect(run(input, config)).rejects.toThrowError(
|
|
'@apply is not supported within nested at-rules like @genie'
|
|
)
|
|
})
|
|
|
|
test('@apply error when using .group utility', async () => {
|
|
let config = {
|
|
darkMode: 'class',
|
|
content: [{ raw: '<div class="foo"></div>' }],
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
@layer components {
|
|
.foo {
|
|
@apply group;
|
|
}
|
|
}
|
|
`
|
|
|
|
await expect(run(input, config)).rejects.toThrowError(
|
|
`@apply should not be used with the 'group' utility`
|
|
)
|
|
})
|
|
|
|
test('@apply error when using a prefixed .group utility', async () => {
|
|
let config = {
|
|
prefix: 'tw-',
|
|
darkMode: 'class',
|
|
content: [{ raw: html`<div class="foo"></div>` }],
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
@layer components {
|
|
.foo {
|
|
@apply tw-group;
|
|
}
|
|
}
|
|
`
|
|
|
|
await expect(run(input, config)).rejects.toThrowError(
|
|
`@apply should not be used with the 'tw-group' utility`
|
|
)
|
|
})
|
|
|
|
test('@apply classes from outside a @layer', async () => {
|
|
let config = {
|
|
content: [{ raw: html`<div class="font-bold foo bar baz"></div>` }],
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
.foo {
|
|
@apply font-bold;
|
|
}
|
|
|
|
.bar {
|
|
@apply foo text-red-500 hover:text-green-500;
|
|
}
|
|
|
|
.baz {
|
|
@apply bar underline;
|
|
}
|
|
|
|
.keep-me-even-though-I-am-not-used-in-content {
|
|
color: green;
|
|
}
|
|
`
|
|
|
|
await run(input, config).then((result) => {
|
|
return expect(result.css).toMatchFormattedCss(css`
|
|
.font-bold {
|
|
font-weight: 700;
|
|
}
|
|
|
|
.foo {
|
|
font-weight: 700;
|
|
}
|
|
|
|
.bar {
|
|
--tw-text-opacity: 1;
|
|
color: rgb(239 68 68 / var(--tw-text-opacity));
|
|
font-weight: 700;
|
|
}
|
|
|
|
.bar:hover {
|
|
--tw-text-opacity: 1;
|
|
color: rgb(34 197 94 / var(--tw-text-opacity));
|
|
}
|
|
|
|
.baz {
|
|
text-decoration-line: underline;
|
|
--tw-text-opacity: 1;
|
|
color: rgb(239 68 68 / var(--tw-text-opacity));
|
|
font-weight: 700;
|
|
}
|
|
|
|
.baz:hover {
|
|
--tw-text-opacity: 1;
|
|
color: rgb(34 197 94 / var(--tw-text-opacity));
|
|
}
|
|
|
|
.keep-me-even-though-I-am-not-used-in-content {
|
|
color: green;
|
|
}
|
|
`)
|
|
})
|
|
})
|
|
|
|
test('@applying classes from outside a @layer respects the source order', async () => {
|
|
let config = {
|
|
content: [{ raw: html`<div class="container font-bold foo bar baz"></div>` }],
|
|
}
|
|
|
|
let input = css`
|
|
.baz {
|
|
@apply bar underline;
|
|
}
|
|
|
|
@tailwind components;
|
|
|
|
.keep-me-even-though-I-am-not-used-in-content {
|
|
color: green;
|
|
}
|
|
|
|
@tailwind utilities;
|
|
|
|
.foo {
|
|
@apply font-bold;
|
|
}
|
|
|
|
.bar {
|
|
@apply no-underline;
|
|
}
|
|
`
|
|
|
|
await run(input, config).then((result) => {
|
|
return expect(result.css).toMatchFormattedCss(css`
|
|
.baz {
|
|
text-decoration-line: underline;
|
|
text-decoration-line: none;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
}
|
|
|
|
.keep-me-even-though-I-am-not-used-in-content {
|
|
color: green;
|
|
}
|
|
|
|
.font-bold {
|
|
font-weight: 700;
|
|
}
|
|
|
|
.foo {
|
|
font-weight: 700;
|
|
}
|
|
|
|
.bar {
|
|
text-decoration-line: none;
|
|
}
|
|
`)
|
|
})
|
|
})
|
|
|
|
it('should remove duplicate properties when using apply with similar properties', () => {
|
|
let config = {
|
|
content: [{ raw: 'foo' }],
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind utilities;
|
|
|
|
.foo {
|
|
@apply absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2;
|
|
}
|
|
`
|
|
|
|
return run(input, config).then((result) => {
|
|
expect(result.css).toMatchFormattedCss(css`
|
|
.foo {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
--tw-translate-x: -50%;
|
|
--tw-translate-y: -50%;
|
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
|
|
skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
|
|
scaleY(var(--tw-scale-y));
|
|
}
|
|
`)
|
|
})
|
|
})
|
|
|
|
it('should apply all the definitions of a class', () => {
|
|
let config = {
|
|
content: [{ raw: html`<div class="foo"></div>` }],
|
|
plugins: [],
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
@layer utilities {
|
|
.aspect-w-1 {
|
|
position: relative;
|
|
}
|
|
|
|
.aspect-w-1 {
|
|
--tw-aspect-w: 1;
|
|
}
|
|
}
|
|
|
|
@layer components {
|
|
.foo {
|
|
@apply aspect-w-1;
|
|
}
|
|
}
|
|
`
|
|
|
|
return run(input, config).then((result) => {
|
|
return expect(result.css).toMatchFormattedCss(css`
|
|
.foo {
|
|
position: relative;
|
|
--tw-aspect-w: 1;
|
|
}
|
|
`)
|
|
})
|
|
})
|
|
|
|
it('should throw when trying to apply a direct circular dependency', () => {
|
|
let config = {
|
|
content: [{ raw: html`<div class="foo"></div>` }],
|
|
plugins: [],
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
@layer components {
|
|
.foo:not(.text-red-500) {
|
|
@apply text-red-500;
|
|
}
|
|
}
|
|
`
|
|
|
|
return run(input, config).catch((err) => {
|
|
expect(err.reason).toBe(
|
|
'You cannot `@apply` the `text-red-500` utility here because it creates a circular dependency.'
|
|
)
|
|
})
|
|
})
|
|
|
|
it('should throw when trying to apply an indirect circular dependency', () => {
|
|
let config = {
|
|
content: [{ raw: html`<div class="a"></div>` }],
|
|
plugins: [],
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
@layer components {
|
|
.a {
|
|
@apply b;
|
|
}
|
|
|
|
.b {
|
|
@apply c;
|
|
}
|
|
|
|
.c {
|
|
@apply a;
|
|
}
|
|
}
|
|
`
|
|
|
|
return run(input, config).catch((err) => {
|
|
expect(err.reason).toBe(
|
|
'You cannot `@apply` the `a` utility here because it creates a circular dependency.'
|
|
)
|
|
})
|
|
})
|
|
|
|
it('should not throw when the selector is different (but contains the base partially)', () => {
|
|
let config = {
|
|
content: [{ raw: html`<div class="bg-gray-500"></div>` }],
|
|
plugins: [],
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
.focus\:bg-gray-500 {
|
|
@apply bg-gray-500;
|
|
}
|
|
`
|
|
|
|
return run(input, config).then((result) => {
|
|
expect(result.css).toMatchFormattedCss(css`
|
|
.bg-gray-500 {
|
|
--tw-bg-opacity: 1;
|
|
background-color: rgb(107 114 128 / var(--tw-bg-opacity));
|
|
}
|
|
|
|
.focus\:bg-gray-500 {
|
|
--tw-bg-opacity: 1;
|
|
background-color: rgb(107 114 128 / var(--tw-bg-opacity));
|
|
}
|
|
`)
|
|
})
|
|
})
|
|
|
|
it('should throw when trying to apply an indirect circular dependency with a modifier (1)', () => {
|
|
let config = {
|
|
content: [{ raw: html`<div class="a"></div>` }],
|
|
plugins: [],
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
@layer components {
|
|
.a {
|
|
@apply b;
|
|
}
|
|
|
|
.b {
|
|
@apply c;
|
|
}
|
|
|
|
.c {
|
|
@apply hover:a;
|
|
}
|
|
}
|
|
`
|
|
|
|
return run(input, config).catch((err) => {
|
|
expect(err.reason).toBe(
|
|
'You cannot `@apply` the `hover:a` utility here because it creates a circular dependency.'
|
|
)
|
|
})
|
|
})
|
|
|
|
it('should throw when trying to apply an indirect circular dependency with a modifier (2)', () => {
|
|
let config = {
|
|
content: [{ raw: html`<div class="a"></div>` }],
|
|
plugins: [],
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
@layer components {
|
|
.a {
|
|
@apply b;
|
|
}
|
|
|
|
.b {
|
|
@apply hover:c;
|
|
}
|
|
|
|
.c {
|
|
@apply a;
|
|
}
|
|
}
|
|
`
|
|
|
|
return run(input, config).catch((err) => {
|
|
expect(err.reason).toBe(
|
|
'You cannot `@apply` the `a` utility here because it creates a circular dependency.'
|
|
)
|
|
})
|
|
})
|
|
|
|
it('rules with vendor prefixes are still separate when optimizing defaults rules', () => {
|
|
let config = {
|
|
experimental: { optimizeUniversalDefaults: true },
|
|
content: [{ raw: html`<div class="border"></div>` }],
|
|
corePlugins: { preflight: false },
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
@layer components {
|
|
input[type='range']::-moz-range-thumb {
|
|
@apply border;
|
|
}
|
|
}
|
|
`
|
|
|
|
return run(input, config).then((result) => {
|
|
return expect(result.css).toMatchFormattedCss(css`
|
|
input[type='range']::-moz-range-thumb {
|
|
border-width: 1px;
|
|
}
|
|
.border {
|
|
border-width: 1px;
|
|
}
|
|
`)
|
|
})
|
|
})
|
|
|
|
it('should be possible to apply user css', () => {
|
|
let config = {
|
|
content: [{ raw: html`<div></div>` }],
|
|
plugins: [],
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
.foo {
|
|
color: red;
|
|
}
|
|
|
|
.bar {
|
|
@apply foo;
|
|
}
|
|
`
|
|
|
|
return run(input, config).then((result) => {
|
|
return expect(result.css).toMatchFormattedCss(css`
|
|
.foo {
|
|
color: red;
|
|
}
|
|
|
|
.bar {
|
|
color: red;
|
|
}
|
|
`)
|
|
})
|
|
})
|
|
|
|
it('should not be possible to apply user css with variants', () => {
|
|
let config = {
|
|
content: [{ raw: html`<div></div>` }],
|
|
plugins: [],
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
.foo {
|
|
color: red;
|
|
}
|
|
|
|
.bar {
|
|
@apply hover:foo;
|
|
}
|
|
`
|
|
|
|
return run(input, config).catch((err) => {
|
|
expect(err.reason).toBe(
|
|
'The `hover:foo` class does not exist. If `hover:foo` is a custom class, make sure it is defined within a `@layer` directive.'
|
|
)
|
|
})
|
|
})
|
|
|
|
it('should not apply unrelated siblings when applying something from within atrules', () => {
|
|
let config = {
|
|
content: [{ raw: html`<div class="foo bar something-unrelated"></div>` }],
|
|
plugins: [],
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
@layer components {
|
|
.foo {
|
|
font-weight: bold;
|
|
@apply bar;
|
|
}
|
|
|
|
.bar {
|
|
color: green;
|
|
}
|
|
|
|
@supports (a: b) {
|
|
.bar {
|
|
color: blue;
|
|
}
|
|
|
|
.something-unrelated {
|
|
color: red;
|
|
}
|
|
}
|
|
}
|
|
`
|
|
|
|
return run(input, config).then((result) => {
|
|
expect(result.css).toMatchFormattedCss(css`
|
|
.foo {
|
|
font-weight: bold;
|
|
color: green;
|
|
}
|
|
|
|
@supports (a: b) {
|
|
.foo {
|
|
color: blue;
|
|
}
|
|
}
|
|
|
|
.bar {
|
|
color: green;
|
|
}
|
|
|
|
@supports (a: b) {
|
|
.bar {
|
|
color: blue;
|
|
}
|
|
|
|
.something-unrelated {
|
|
color: red;
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
})
|
|
|
|
it('should be possible to apply user css without tailwind directives', () => {
|
|
let config = {
|
|
content: [{ raw: html`<div class="foo"></div>` }],
|
|
plugins: [],
|
|
}
|
|
|
|
let input = css`
|
|
.bop {
|
|
color: red;
|
|
}
|
|
.bar {
|
|
background-color: blue;
|
|
}
|
|
.foo {
|
|
@apply absolute bar bop;
|
|
}
|
|
`
|
|
|
|
return run(input, config).then((result) => {
|
|
return expect(result.css).toMatchFormattedCss(css`
|
|
.bop {
|
|
color: red;
|
|
}
|
|
.bar {
|
|
background-color: blue;
|
|
}
|
|
.foo {
|
|
position: absolute;
|
|
color: red;
|
|
background-color: blue;
|
|
}
|
|
`)
|
|
})
|
|
})
|
|
|
|
it('should be possible to apply a class from another rule with multiple selectors (2 classes)', () => {
|
|
let config = {
|
|
content: [{ raw: html`<div class="c"></div>` }],
|
|
plugins: [],
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind utilities;
|
|
@layer utilities {
|
|
.a,
|
|
.b {
|
|
@apply underline;
|
|
}
|
|
|
|
.c {
|
|
@apply b;
|
|
}
|
|
}
|
|
`
|
|
|
|
return run(input, config).then((result) => {
|
|
return expect(result.css).toMatchFormattedCss(css`
|
|
.c {
|
|
text-decoration-line: underline;
|
|
}
|
|
`)
|
|
})
|
|
})
|
|
|
|
it('should be possible to apply a class from another rule with multiple selectors (1 class, 1 tag)', () => {
|
|
let config = {
|
|
content: [{ raw: html`<div class="c"></div>` }],
|
|
plugins: [],
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind utilities;
|
|
|
|
@layer utilities {
|
|
span,
|
|
.b {
|
|
@apply underline;
|
|
}
|
|
|
|
.c {
|
|
@apply b;
|
|
}
|
|
}
|
|
`
|
|
|
|
return run(input, config).then((result) => {
|
|
return expect(result.css).toMatchFormattedCss(css`
|
|
span,
|
|
.b {
|
|
text-decoration-line: underline;
|
|
}
|
|
|
|
.c {
|
|
text-decoration-line: underline;
|
|
}
|
|
`)
|
|
})
|
|
})
|
|
|
|
it('should be possible to apply a class from another rule with multiple selectors (1 class, 1 id)', () => {
|
|
let config = {
|
|
content: [{ raw: html`<div class="c"></div>` }],
|
|
plugins: [],
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind utilities;
|
|
@layer utilities {
|
|
#a,
|
|
.b {
|
|
@apply underline;
|
|
}
|
|
|
|
.c {
|
|
@apply b;
|
|
}
|
|
}
|
|
`
|
|
|
|
return run(input, config).then((result) => {
|
|
return expect(result.css).toMatchFormattedCss(css`
|
|
#a,
|
|
.b {
|
|
text-decoration-line: underline;
|
|
}
|
|
|
|
.c {
|
|
text-decoration-line: underline;
|
|
}
|
|
`)
|
|
})
|
|
})
|
|
|
|
describe('multiple instances', () => {
|
|
it('should be possible to apply multiple "instances" of the same class', () => {
|
|
let config = {
|
|
content: [{ raw: html`` }],
|
|
plugins: [],
|
|
corePlugins: { preflight: false },
|
|
}
|
|
|
|
let input = css`
|
|
.a {
|
|
@apply b;
|
|
}
|
|
|
|
.b {
|
|
@apply uppercase;
|
|
}
|
|
|
|
.b {
|
|
color: red;
|
|
}
|
|
`
|
|
|
|
return run(input, config).then((result) => {
|
|
return expect(result.css).toMatchFormattedCss(css`
|
|
.a {
|
|
text-transform: uppercase;
|
|
color: red;
|
|
}
|
|
|
|
.b {
|
|
text-transform: uppercase;
|
|
color: red;
|
|
}
|
|
`)
|
|
})
|
|
})
|
|
|
|
it('should be possible to apply a combination of multiple "instances" of the same class', () => {
|
|
let config = {
|
|
content: [{ raw: html`` }],
|
|
plugins: [],
|
|
corePlugins: { preflight: false },
|
|
}
|
|
|
|
let input = css`
|
|
.a {
|
|
@apply b;
|
|
}
|
|
|
|
.b {
|
|
@apply uppercase;
|
|
color: red;
|
|
}
|
|
`
|
|
|
|
return run(input, config).then((result) => {
|
|
return expect(result.css).toMatchFormattedCss(css`
|
|
.a {
|
|
text-transform: uppercase;
|
|
color: red;
|
|
}
|
|
|
|
.b {
|
|
text-transform: uppercase;
|
|
color: red;
|
|
}
|
|
`)
|
|
})
|
|
})
|
|
|
|
it('should generate the same output, even if it was used in a @layer', () => {
|
|
let config = {
|
|
content: [{ raw: html`<div class="a b"></div>` }],
|
|
plugins: [],
|
|
corePlugins: { preflight: false },
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind components;
|
|
|
|
@layer components {
|
|
.a {
|
|
@apply b;
|
|
}
|
|
|
|
.b {
|
|
@apply uppercase;
|
|
color: red;
|
|
}
|
|
}
|
|
`
|
|
|
|
return run(input, config).then((result) => {
|
|
return expect(result.css).toMatchFormattedCss(css`
|
|
.a {
|
|
text-transform: uppercase;
|
|
color: red;
|
|
}
|
|
|
|
.b {
|
|
text-transform: uppercase;
|
|
color: red;
|
|
}
|
|
`)
|
|
})
|
|
})
|
|
|
|
it('should be possible to apply a combination of multiple "instances" of the same class (defined in a layer)', () => {
|
|
let config = {
|
|
content: [{ raw: html`<div class="a b"></div>` }],
|
|
plugins: [],
|
|
corePlugins: { preflight: false },
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind components;
|
|
|
|
@layer components {
|
|
.a {
|
|
color: red;
|
|
@apply b;
|
|
color: blue;
|
|
}
|
|
|
|
.b {
|
|
@apply text-green-500;
|
|
text-decoration: underline;
|
|
}
|
|
}
|
|
`
|
|
|
|
return run(input, config).then((result) => {
|
|
return expect(result.css).toMatchFormattedCss(css`
|
|
.a {
|
|
color: red;
|
|
--tw-text-opacity: 1;
|
|
color: rgb(34 197 94 / var(--tw-text-opacity));
|
|
text-decoration: underline;
|
|
color: blue;
|
|
}
|
|
|
|
.b {
|
|
--tw-text-opacity: 1;
|
|
color: rgb(34 197 94 / var(--tw-text-opacity));
|
|
text-decoration: underline;
|
|
}
|
|
`)
|
|
})
|
|
})
|
|
|
|
it('should properly maintain the order', () => {
|
|
let config = {
|
|
content: [{ raw: html`` }],
|
|
plugins: [],
|
|
corePlugins: { preflight: false },
|
|
}
|
|
|
|
let input = css`
|
|
h2 {
|
|
@apply text-xl;
|
|
@apply lg:text-3xl;
|
|
@apply sm:text-2xl;
|
|
}
|
|
`
|
|
|
|
return run(input, config).then((result) => {
|
|
return expect(result.css).toMatchFormattedCss(css`
|
|
h2 {
|
|
font-size: 1.25rem;
|
|
line-height: 1.75rem;
|
|
}
|
|
|
|
@media (min-width: 1024px) {
|
|
h2 {
|
|
font-size: 1.875rem;
|
|
line-height: 2.25rem;
|
|
}
|
|
}
|
|
|
|
@media (min-width: 640px) {
|
|
h2 {
|
|
font-size: 1.5rem;
|
|
line-height: 2rem;
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
})
|
|
})
|
|
|
|
it('apply can emit defaults in isolated environments without @tailwind directives', () => {
|
|
let config = {
|
|
experimental: { optimizeUniversalDefaults: true },
|
|
|
|
content: [{ raw: html`<div class="foo"></div>` }],
|
|
}
|
|
|
|
let input = css`
|
|
.foo {
|
|
@apply focus:rotate-90;
|
|
}
|
|
`
|
|
|
|
return run(input, config).then((result) => {
|
|
return expect(result.css).toMatchFormattedCss(css`
|
|
.foo:focus {
|
|
--tw-rotate: 90deg;
|
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
|
|
skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
|
|
scaleY(var(--tw-scale-y));
|
|
}
|
|
`)
|
|
})
|
|
})
|
|
|
|
it('apply does not emit defaults in isolated environments without optimizeUniversalDefaults', () => {
|
|
let config = {
|
|
experimental: { optimizeUniversalDefaults: false },
|
|
content: [{ raw: html`<div class="foo"></div>` }],
|
|
corePlugins: { preflight: false },
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind base;
|
|
|
|
.foo {
|
|
@apply focus:rotate-90;
|
|
}
|
|
`
|
|
|
|
return run(input, config).then((result) => {
|
|
return expect(result.css).toMatchFormattedCss(css`
|
|
${defaults}
|
|
.foo:focus {
|
|
--tw-rotate: 90deg;
|
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate))
|
|
skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
|
|
scaleY(var(--tw-scale-y));
|
|
}
|
|
`)
|
|
})
|
|
})
|
|
|
|
it('should work outside of layer', async () => {
|
|
let config = {
|
|
content: [{ raw: html`<div class="input-text"></div>` }],
|
|
corePlugins: { preflight: false },
|
|
}
|
|
|
|
let input = css`
|
|
.input-text {
|
|
@apply bg-white;
|
|
background-color: red;
|
|
}
|
|
`
|
|
|
|
let result
|
|
result = await run(input, config)
|
|
|
|
expect(result.css).toMatchFormattedCss(css`
|
|
.input-text {
|
|
--tw-bg-opacity: 1;
|
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
|
background-color: red;
|
|
}
|
|
`)
|
|
|
|
result = await run(input, config)
|
|
|
|
expect(result.css).toMatchFormattedCss(css`
|
|
.input-text {
|
|
--tw-bg-opacity: 1;
|
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
|
background-color: red;
|
|
}
|
|
`)
|
|
})
|
|
|
|
it('should work in layer', async () => {
|
|
let config = {
|
|
content: [{ raw: html`<div class="input-text"></div>` }],
|
|
corePlugins: { preflight: false },
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind components;
|
|
@layer components {
|
|
.input-text {
|
|
@apply bg-white;
|
|
background-color: red;
|
|
}
|
|
}
|
|
`
|
|
|
|
await run(input, config)
|
|
const result = await run(input, config)
|
|
|
|
expect(result.css).toMatchFormattedCss(css`
|
|
.input-text {
|
|
--tw-bg-opacity: 1;
|
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
|
background-color: red;
|
|
}
|
|
`)
|
|
})
|
|
|
|
it('apply partitioning works with media queries', async () => {
|
|
let config = {
|
|
content: [{ raw: html`` }],
|
|
corePlugins: { preflight: false },
|
|
}
|
|
|
|
let input = css`
|
|
@tailwind base;
|
|
@layer base {
|
|
html,
|
|
body {
|
|
@apply text-green-600;
|
|
font-size: 1rem;
|
|
}
|
|
|
|
@media print {
|
|
html,
|
|
body {
|
|
@apply text-red-600;
|
|
font-size: 2rem;
|
|
}
|
|
}
|
|
}
|
|
`
|
|
|
|
await run(input, config)
|
|
const result = await run(input, config)
|
|
|
|
expect(result.css).toMatchFormattedCss(css`
|
|
html,
|
|
body {
|
|
--tw-text-opacity: 1;
|
|
color: rgb(22 163 74 / var(--tw-text-opacity));
|
|
font-size: 1rem;
|
|
}
|
|
|
|
@media print {
|
|
html,
|
|
body {
|
|
--tw-text-opacity: 1;
|
|
color: rgb(220 38 38 / var(--tw-text-opacity));
|
|
}
|
|
html,
|
|
body {
|
|
font-size: 2rem;
|
|
}
|
|
}
|
|
${defaults}
|
|
`)
|
|
})
|