1403 lines
28 KiB
JavaScript
Executable File
1403 lines
28 KiB
JavaScript
Executable File
import postcss from 'postcss'
|
|
import resolveConfig from '../src/util/resolveConfig'
|
|
import defaultConfig from '../stubs/defaultConfig.stub.js'
|
|
import tailwind from '../src/index'
|
|
|
|
function run(input, config = {}) {
|
|
return postcss([tailwind(config)]).process(input, { from: undefined })
|
|
}
|
|
|
|
test('it copies class declarations into itself', () => {
|
|
const output = '.a { color: red; } .b { color: red; }'
|
|
|
|
expect.assertions(2)
|
|
|
|
return run('.a { color: red; } .b { @apply a; }').then((result) => {
|
|
expect(result.css).toMatchCss(output)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('apply values can contain tabs', () => {
|
|
const input = `
|
|
.a {
|
|
@apply p-4\tm-4;
|
|
}
|
|
`
|
|
|
|
const expected = `
|
|
.a {
|
|
margin: 1rem;
|
|
padding: 1rem;
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('apply values can contain newlines', () => {
|
|
const input = `
|
|
.a {
|
|
@apply p-4 m-4
|
|
flex flex-col;
|
|
}
|
|
`
|
|
|
|
const expected = `
|
|
.a {
|
|
display: flex;
|
|
flex-direction: column;
|
|
margin: 1rem;
|
|
padding: 1rem;
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('selectors with invalid characters do not need to be manually escaped', () => {
|
|
const input = `
|
|
.a\\:1\\/2 { color: red; }
|
|
.b { @apply a:1/2; }
|
|
`
|
|
|
|
const expected = `
|
|
.a\\:1\\/2 { color: red; }
|
|
.b { color: red; }
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('it removes important from applied classes by default', () => {
|
|
const input = `
|
|
.a { color: red !important; }
|
|
.a:hover { color: blue !important; }
|
|
.b { @apply a; }
|
|
`
|
|
|
|
const expected = `
|
|
.a { color: red !important; }
|
|
.a:hover { color: blue !important; }
|
|
.b { color: red; }
|
|
.b:hover { color: blue; }
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('applied rules can be made !important', () => {
|
|
const input = `
|
|
.a { color: red; }
|
|
.b { @apply a !important; }
|
|
`
|
|
|
|
const expected = `
|
|
.a { color: red; }
|
|
.b { color: red !important; }
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('cssnext custom property sets are no longer supported', () => {
|
|
const input = `
|
|
.a {
|
|
color: red;
|
|
}
|
|
.b {
|
|
@apply a --custom-property-set;
|
|
}
|
|
`
|
|
|
|
expect.assertions(1)
|
|
|
|
return run(input).catch((e) => {
|
|
expect(e).toMatchObject({ name: 'CssSyntaxError' })
|
|
})
|
|
})
|
|
|
|
test('it fails if the class does not exist', () => {
|
|
expect.assertions(1)
|
|
|
|
return run('.b { @apply a; }').catch((e) => {
|
|
expect(e).toMatchObject({ name: 'CssSyntaxError' })
|
|
})
|
|
})
|
|
|
|
test('applying classes that are defined in a media query is supported', () => {
|
|
const input = `
|
|
@media (min-width: 300px) {
|
|
.a { color: blue; }
|
|
}
|
|
|
|
.b {
|
|
@apply a;
|
|
}
|
|
`
|
|
|
|
const output = `
|
|
@media (min-width: 300px) {
|
|
.a { color: blue; }
|
|
}
|
|
@media (min-width: 300px) {
|
|
.b { color: blue; }
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(output)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('applying classes that are used in a media query is supported', () => {
|
|
const input = `
|
|
.a {
|
|
color: red;
|
|
}
|
|
|
|
@media (min-width: 300px) {
|
|
.a { color: blue; }
|
|
}
|
|
|
|
.b {
|
|
@apply a;
|
|
}
|
|
`
|
|
|
|
const output = `
|
|
.a {
|
|
color: red;
|
|
}
|
|
|
|
@media (min-width: 300px) {
|
|
.a { color: blue; }
|
|
}
|
|
|
|
.b {
|
|
color: red;
|
|
}
|
|
|
|
@media (min-width: 300px) {
|
|
.b { color: blue; }
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(output)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('it matches classes that include pseudo-selectors', () => {
|
|
const input = `
|
|
.a:hover {
|
|
color: red;
|
|
}
|
|
|
|
.b {
|
|
@apply a;
|
|
}
|
|
`
|
|
|
|
const output = `
|
|
.a:hover {
|
|
color: red;
|
|
}
|
|
|
|
.b:hover {
|
|
color: red;
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(output)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('it matches classes that have multiple rules', () => {
|
|
const input = `
|
|
.a {
|
|
color: red;
|
|
}
|
|
|
|
.b {
|
|
@apply a;
|
|
}
|
|
|
|
.a {
|
|
color: blue;
|
|
}
|
|
`
|
|
|
|
const output = `
|
|
.a {
|
|
color: red;
|
|
}
|
|
|
|
.b {
|
|
color: red;
|
|
color: blue;
|
|
}
|
|
|
|
.a {
|
|
color: blue;
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(output)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('applying a class that appears multiple times in one selector', () => {
|
|
const input = `
|
|
.a + .a > .a {
|
|
color: red;
|
|
}
|
|
|
|
.b {
|
|
@apply a;
|
|
}
|
|
`
|
|
|
|
const output = `
|
|
.a + .a > .a {
|
|
color: red;
|
|
}
|
|
.b + .a > .a {
|
|
color: red;
|
|
}
|
|
.a + .b > .a {
|
|
color: red;
|
|
}
|
|
.a + .a > .b {
|
|
color: red;
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(output)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('you can apply utility classes that do not actually exist as long as they would exist if utilities were being generated', () => {
|
|
const input = `
|
|
.foo { @apply mt-4; }
|
|
`
|
|
|
|
const expected = `
|
|
.foo { margin-top: 1rem; }
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('shadow lookup will be constructed when we have missing @tailwind atrules', () => {
|
|
const input = `
|
|
@tailwind base;
|
|
|
|
.foo { @apply mt-4; }
|
|
`
|
|
|
|
expect.assertions(1)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toContain(`.foo { margin-top: 1rem;\n}`)
|
|
})
|
|
})
|
|
|
|
test('you can apply a class that is defined in multiple rules', () => {
|
|
const input = `
|
|
.foo {
|
|
color: red;
|
|
}
|
|
.bar {
|
|
@apply foo;
|
|
}
|
|
.foo {
|
|
opacity: .5;
|
|
}
|
|
`
|
|
const expected = `
|
|
.foo {
|
|
color: red;
|
|
}
|
|
.bar {
|
|
color: red;
|
|
opacity: .5;
|
|
}
|
|
.foo {
|
|
opacity: .5;
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('you can apply a class that is defined in a media query', () => {
|
|
const input = `
|
|
.foo {
|
|
@apply sm:text-center;
|
|
}
|
|
`
|
|
const expected = `
|
|
@media (min-width: 640px) {
|
|
.foo {
|
|
text-align: center
|
|
}
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('you can apply pseudo-class variant utilities', () => {
|
|
const input = `
|
|
.foo {
|
|
@apply hover:opacity-50;
|
|
}
|
|
`
|
|
const expected = `
|
|
.foo:hover {
|
|
opacity: 0.5
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('you can apply responsive pseudo-class variant utilities', () => {
|
|
const input = `
|
|
.foo {
|
|
@apply sm:hover:opacity-50;
|
|
}
|
|
`
|
|
const expected = `
|
|
@media (min-width: 640px) {
|
|
.foo:hover {
|
|
opacity: 0.5
|
|
}
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('you can apply the container component', () => {
|
|
const input = `
|
|
.foo {
|
|
@apply container;
|
|
}
|
|
`
|
|
const expected = `
|
|
.foo {
|
|
width: 100%;
|
|
}
|
|
@media (min-width: 640px) {
|
|
.foo {
|
|
max-width: 640px;
|
|
}
|
|
}
|
|
@media (min-width: 768px) {
|
|
.foo {
|
|
max-width: 768px;
|
|
}
|
|
}
|
|
@media (min-width: 1024px) {
|
|
.foo {
|
|
max-width: 1024px;
|
|
}
|
|
}
|
|
@media (min-width: 1280px) {
|
|
.foo {
|
|
max-width: 1280px;
|
|
}
|
|
}
|
|
@media (min-width: 1536px) {
|
|
.foo {
|
|
max-width: 1536px;
|
|
}
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('classes are applied according to CSS source order, not apply order', () => {
|
|
const input = `
|
|
.foo {
|
|
color: red;
|
|
}
|
|
.bar {
|
|
color: blue;
|
|
}
|
|
.baz {
|
|
@apply bar foo;
|
|
}
|
|
`
|
|
const expected = `
|
|
.foo {
|
|
color: red;
|
|
}
|
|
.bar {
|
|
color: blue;
|
|
}
|
|
.baz {
|
|
color: red;
|
|
color: blue;
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('you can apply utilities with multi-class selectors like group-hover variants', () => {
|
|
const input = `
|
|
.foo {
|
|
@apply group-hover:bar;
|
|
}
|
|
.bar {
|
|
color: blue;
|
|
}
|
|
.group:hover .group-hover\\:bar {
|
|
color: blue;
|
|
}
|
|
`
|
|
const expected = `
|
|
.group:hover .foo {
|
|
color: blue;
|
|
}
|
|
.bar {
|
|
color: blue;
|
|
}
|
|
.group:hover .group-hover\\:bar {
|
|
color: blue;
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('you can apply classes recursively', () => {
|
|
const input = `
|
|
.baz {
|
|
color: blue;
|
|
}
|
|
.bar {
|
|
@apply baz px-4;
|
|
}
|
|
.foo {
|
|
@apply bar;
|
|
}
|
|
`
|
|
const expected = `
|
|
.baz {
|
|
color: blue;
|
|
}
|
|
.bar {
|
|
padding-left: 1rem;
|
|
padding-right: 1rem;
|
|
color: blue;
|
|
}
|
|
.foo {
|
|
padding-left: 1rem;
|
|
padding-right: 1rem;
|
|
color: blue;
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('you can apply complex classes recursively', () => {
|
|
const input = `
|
|
.button {
|
|
@apply rounded-xl px-6 py-2 hover:text-white focus:border-opacity-100;
|
|
}
|
|
|
|
.button-yellow {
|
|
@apply button bg-yellow-600 text-gray-200;
|
|
}
|
|
`
|
|
|
|
const expected = `
|
|
.button:focus {
|
|
--tw-border-opacity: 1;
|
|
}
|
|
|
|
.button {
|
|
border-radius: 0.75rem;
|
|
padding-top: 0.5rem;
|
|
padding-bottom: 0.5rem;
|
|
padding-left: 1.5rem;
|
|
padding-right: 1.5rem;
|
|
}
|
|
|
|
.button:hover {
|
|
--tw-text-opacity: 1;
|
|
color: rgba(255, 255, 255, var(--tw-text-opacity));
|
|
}
|
|
|
|
.button-yellow {
|
|
--tw-bg-opacity: 1;
|
|
background-color: rgba(217, 119, 6, var(--tw-bg-opacity));
|
|
--tw-text-opacity: 1;
|
|
color: rgba(229, 231, 235, var(--tw-text-opacity));
|
|
}
|
|
|
|
.button-yellow:focus {
|
|
--tw-border-opacity: 1;
|
|
}
|
|
|
|
.button-yellow {
|
|
border-radius: 0.75rem;
|
|
padding-top: 0.5rem;
|
|
padding-bottom: 0.5rem;
|
|
padding-left: 1.5rem;
|
|
padding-right: 1.5rem;
|
|
}
|
|
|
|
.button-yellow:hover {
|
|
--tw-text-opacity: 1;
|
|
color: rgba(255, 255, 255, var(--tw-text-opacity));
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('you can apply classes recursively out of order', () => {
|
|
const input = `
|
|
.foo {
|
|
@apply bar;
|
|
}
|
|
.bar {
|
|
@apply baz;
|
|
}
|
|
.baz {
|
|
color: blue;
|
|
}
|
|
`
|
|
const expected = `
|
|
.foo {
|
|
color: blue;
|
|
}
|
|
.bar {
|
|
color: blue;
|
|
}
|
|
.baz {
|
|
color: blue;
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('applied classes are always inserted before subsequent declarations in the same rule, even if it means moving those subsequent declarations to a new rule', () => {
|
|
const input = `
|
|
.foo {
|
|
background: blue;
|
|
@apply opacity-50 hover:opacity-100 text-right sm:align-middle;
|
|
color: red;
|
|
}
|
|
`
|
|
const expected = `
|
|
.foo {
|
|
background: blue;
|
|
opacity: 0.5;
|
|
}
|
|
.foo:hover {
|
|
opacity: 1;
|
|
}
|
|
.foo {
|
|
text-align: right;
|
|
}
|
|
@media (min-width: 640px) {
|
|
.foo {
|
|
vertical-align: middle;
|
|
}
|
|
}
|
|
.foo {
|
|
color: red;
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('adjacent rules are collapsed after being applied', () => {
|
|
const input = `
|
|
.foo {
|
|
@apply hover:bg-white hover:opacity-50 absolute text-right sm:align-middle sm:text-center;
|
|
}
|
|
`
|
|
const expected = `
|
|
.foo:hover {
|
|
--tw-bg-opacity: 1;
|
|
background-color: rgba(255, 255, 255, var(--tw-bg-opacity));
|
|
opacity: 0.5;
|
|
}
|
|
.foo {
|
|
position: absolute;
|
|
text-align: right;
|
|
}
|
|
@media (min-width: 640px) {
|
|
.foo {
|
|
text-align: center;
|
|
vertical-align: middle;
|
|
}
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('applying a class applies all instances of that class, even complex selectors', () => {
|
|
const input = `
|
|
h1 > p:hover .banana:first-child * {
|
|
@apply bar;
|
|
}
|
|
.bar {
|
|
color: blue;
|
|
}
|
|
@media (print) {
|
|
@supports (display: grid) {
|
|
.baz .bar:hover {
|
|
text-align: right;
|
|
float: left;
|
|
}
|
|
}
|
|
}
|
|
`
|
|
const expected = `
|
|
h1 > p:hover .banana:first-child * {
|
|
color: blue;
|
|
}
|
|
@media (print) {
|
|
@supports (display: grid) {
|
|
.baz h1 > p:hover .banana:first-child *:hover {
|
|
text-align: right;
|
|
float: left;
|
|
}
|
|
}
|
|
}
|
|
.bar {
|
|
color: blue;
|
|
}
|
|
@media (print) {
|
|
@supports (display: grid) {
|
|
.baz .bar:hover {
|
|
text-align: right;
|
|
float: left;
|
|
}
|
|
}
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('you can apply classes to rules within at-rules', () => {
|
|
const input = `
|
|
@supports (display: grid) {
|
|
.baz .bar {
|
|
@apply text-right float-left hover:opacity-50 md:float-right;
|
|
}
|
|
}
|
|
`
|
|
const expected = `
|
|
@supports (display: grid) {
|
|
.baz .bar {
|
|
float: left;
|
|
}
|
|
.baz .bar:hover {
|
|
opacity: 0.5;
|
|
}
|
|
.baz .bar {
|
|
text-align: right;
|
|
}
|
|
@media (min-width: 768px) {
|
|
.baz .bar {
|
|
float: right;
|
|
}
|
|
}
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
describe('using apply with the prefix option', () => {
|
|
test('applying a class including the prefix', () => {
|
|
const input = `
|
|
.foo { @apply tw-mt-4; }
|
|
`
|
|
|
|
const expected = `
|
|
.foo { margin-top: 1rem; }
|
|
`
|
|
|
|
const config = resolveConfig([
|
|
{
|
|
...defaultConfig,
|
|
prefix: 'tw-',
|
|
},
|
|
])
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input, config).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('applying a class including the prefix when using a prefix function', () => {
|
|
const input = `
|
|
.foo { @apply tw-func-mt-4; }
|
|
`
|
|
|
|
const expected = `
|
|
.foo { margin-top: 1rem; }
|
|
`
|
|
|
|
const config = resolveConfig([
|
|
{
|
|
...defaultConfig,
|
|
prefix: () => {
|
|
return 'tw-func-'
|
|
},
|
|
},
|
|
])
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input, config).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('applying a class without the prefix fails', () => {
|
|
const input = `
|
|
.foo { @apply mt-4; }
|
|
`
|
|
|
|
const config = resolveConfig([
|
|
{
|
|
...defaultConfig,
|
|
prefix: 'tw-',
|
|
},
|
|
])
|
|
|
|
expect.assertions(1)
|
|
|
|
return run(input, config).catch((e) => {
|
|
expect(e).toMatchObject({ name: 'CssSyntaxError' })
|
|
})
|
|
})
|
|
|
|
test('custom classes with no prefix can be applied', () => {
|
|
const input = `
|
|
.foo { @apply mt-4; }
|
|
.mt-4 { color: red; }
|
|
`
|
|
|
|
const expected = `
|
|
.foo { color: red; }
|
|
.mt-4 { color: red; }
|
|
`
|
|
|
|
const config = resolveConfig([
|
|
{
|
|
...defaultConfig,
|
|
prefix: 'tw-',
|
|
},
|
|
])
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input, config).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('built-in prefixed utilities can be extended and applied', () => {
|
|
const input = `
|
|
.foo { @apply tw-mt-4; }
|
|
.tw-mt-4 { color: red; }
|
|
`
|
|
|
|
const expected = `
|
|
.foo { margin-top: 1rem; color: red; }
|
|
.tw-mt-4 { color: red; }
|
|
`
|
|
|
|
const config = resolveConfig([
|
|
{
|
|
...defaultConfig,
|
|
prefix: 'tw-',
|
|
},
|
|
])
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input, config).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('a helpful error message is provided if it appears the user forgot to include their prefix', () => {
|
|
const input = `
|
|
.foo { @apply mt-4; }
|
|
`
|
|
|
|
const config = resolveConfig([
|
|
{
|
|
...defaultConfig,
|
|
prefix: 'tw-',
|
|
},
|
|
])
|
|
|
|
expect.assertions(1)
|
|
|
|
return run(input, config).catch((e) => {
|
|
expect(e).toMatchObject({
|
|
name: 'CssSyntaxError',
|
|
reason: 'The `mt-4` class does not exist, but `tw-mt-4` does. Did you forget the prefix?',
|
|
})
|
|
})
|
|
})
|
|
|
|
test('a "Did You Mean" suggestion is recommended if a similar class can be identified.', () => {
|
|
const input = `
|
|
.foo { @apply anti-aliased; }
|
|
`
|
|
|
|
const config = resolveConfig([
|
|
{
|
|
...defaultConfig,
|
|
},
|
|
])
|
|
|
|
expect.assertions(1)
|
|
|
|
return run(input, config).catch((e) => {
|
|
expect(e).toMatchObject({
|
|
name: 'CssSyntaxError',
|
|
reason:
|
|
"The `anti-aliased` class does not exist, but `antialiased` does. If you're sure that `anti-aliased` exists, make sure that any `@import` statements are being properly processed before Tailwind CSS sees your CSS, as `@apply` can only be used for classes in the same CSS tree.",
|
|
})
|
|
})
|
|
})
|
|
|
|
test('a "Did You Mean" suggestion is omitted if a similar class cannot be identified.', () => {
|
|
const input = `
|
|
.foo { @apply anteater; }
|
|
`
|
|
|
|
const config = resolveConfig([{ ...defaultConfig }])
|
|
|
|
expect.assertions(1)
|
|
|
|
return run(input, config).catch((e) => {
|
|
expect(e).toMatchObject({
|
|
name: 'CssSyntaxError',
|
|
reason:
|
|
"The `anteater` class does not exist. If you're sure that `anteater` exists, make sure that any `@import` statements are being properly processed before Tailwind CSS sees your CSS, as `@apply` can only be used for classes in the same CSS tree.",
|
|
})
|
|
})
|
|
})
|
|
|
|
test('you can apply classes with important and a prefix enabled', () => {
|
|
const input = `
|
|
.foo { @apply tw-mt-4; }
|
|
`
|
|
|
|
const expected = `
|
|
.foo { margin-top: 1rem; }
|
|
`
|
|
|
|
const config = resolveConfig([
|
|
{
|
|
...defaultConfig,
|
|
prefix: 'tw-',
|
|
important: true,
|
|
},
|
|
])
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input, config).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('you can apply classes with an important selector and a prefix enabled', () => {
|
|
const input = `
|
|
.foo { @apply tw-mt-4; }
|
|
`
|
|
|
|
const expected = `
|
|
.foo { margin-top: 1rem; }
|
|
`
|
|
|
|
const config = resolveConfig([
|
|
{
|
|
...defaultConfig,
|
|
prefix: 'tw-',
|
|
important: '#app',
|
|
},
|
|
])
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input, config).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
})
|
|
|
|
test('you can apply utility classes when a selector is used for the important option', () => {
|
|
const input = `
|
|
.foo {
|
|
@apply mt-8 mb-8;
|
|
}
|
|
`
|
|
|
|
const expected = `
|
|
.foo {
|
|
margin-top: 2rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
`
|
|
|
|
const config = resolveConfig([
|
|
{
|
|
...defaultConfig,
|
|
important: '#app',
|
|
},
|
|
])
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input, config).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('you can apply classes to a rule with multiple selectors', () => {
|
|
const input = `
|
|
@supports (display: grid) {
|
|
.foo, h1 > .bar * {
|
|
@apply float-left opacity-50 hover:opacity-100 md:float-right;
|
|
}
|
|
}
|
|
`
|
|
|
|
const expected = `
|
|
@supports (display: grid) {
|
|
.foo, h1 > .bar * {
|
|
float: left;
|
|
opacity: 0.5;
|
|
}
|
|
.foo:hover, h1 > .bar *:hover {
|
|
opacity: 1;
|
|
}
|
|
@media (min-width: 768px) {
|
|
.foo, h1 > .bar * {
|
|
float: right;
|
|
}
|
|
}
|
|
}
|
|
`
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('you can apply classes to a rule with multiple selectors with important and a prefix enabled', () => {
|
|
const input = `
|
|
@supports (display: grid) {
|
|
.foo, h1 > .bar * {
|
|
@apply tw-float-left tw-opacity-50 hover:tw-opacity-100 md:tw-float-right;
|
|
}
|
|
}
|
|
`
|
|
|
|
const expected = `
|
|
@supports (display: grid) {
|
|
.foo, h1 > .bar * {
|
|
float: left;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.foo:hover, h1 > .bar *:hover {
|
|
opacity: 1;
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.foo, h1 > .bar * {
|
|
float: right;
|
|
}
|
|
}
|
|
}
|
|
`
|
|
|
|
const config = resolveConfig([
|
|
{
|
|
...defaultConfig,
|
|
prefix: 'tw-',
|
|
important: true,
|
|
},
|
|
])
|
|
|
|
return run(input, config).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('you can apply classes to multiple selectors at the same time, removing important', () => {
|
|
const input = `
|
|
.multiple p,
|
|
.multiple ul,
|
|
.multiple ol {
|
|
@apply mt-5;
|
|
}
|
|
|
|
.multiple h2,
|
|
.multiple h3,
|
|
.multiple h4 {
|
|
@apply mt-8;
|
|
}
|
|
`
|
|
|
|
const expected = `
|
|
.multiple p,
|
|
.multiple ul,
|
|
.multiple ol {
|
|
margin-top: 1.25rem;
|
|
}
|
|
|
|
.multiple h2,
|
|
.multiple h3,
|
|
.multiple h4 {
|
|
margin-top: 2rem;
|
|
}
|
|
`
|
|
|
|
const config = resolveConfig([{ ...defaultConfig, important: true }])
|
|
|
|
return run(input, config).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('you can apply classes in a nested rule', () => {
|
|
const input = `
|
|
.selector {
|
|
&:hover {
|
|
@apply text-white;
|
|
}
|
|
}
|
|
`
|
|
|
|
const expected = `
|
|
.selector {
|
|
&:hover {
|
|
--tw-text-opacity: 1;
|
|
color: rgba(255, 255, 255, var(--tw-text-opacity));
|
|
}
|
|
}
|
|
`
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('you can apply classes in a nested @atrule', () => {
|
|
const input = `
|
|
.selector {
|
|
@media (min-width: 200px) {
|
|
@apply overflow-hidden;
|
|
}
|
|
}
|
|
`
|
|
|
|
const expected = `
|
|
.selector {
|
|
@media (min-width: 200px) {
|
|
overflow: hidden;
|
|
}
|
|
}
|
|
`
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('you can apply classes in a custom nested @atrule', () => {
|
|
const input = `
|
|
.selector {
|
|
@screen md {
|
|
@apply w-2/6;
|
|
}
|
|
}
|
|
`
|
|
|
|
const expected = `
|
|
.selector {
|
|
@media (min-width: 768px) {
|
|
width: 33.333333%;
|
|
}
|
|
}
|
|
`
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('you can deeply apply classes in a custom nested @atrule', () => {
|
|
const input = `
|
|
.selector {
|
|
.subselector {
|
|
@screen md {
|
|
@apply w-2/6;
|
|
}
|
|
}
|
|
}
|
|
`
|
|
|
|
const expected = `
|
|
.selector {
|
|
.subselector {
|
|
@media (min-width: 768px) {
|
|
width: 33.333333%
|
|
}
|
|
}
|
|
}
|
|
`
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('declarations within a rule that uses @apply can be !important', () => {
|
|
const input = `
|
|
.foo {
|
|
@apply text-center;
|
|
float: left;
|
|
display: block !important;
|
|
}
|
|
`
|
|
|
|
const expected = `
|
|
.foo {
|
|
text-align: center;
|
|
float: left;
|
|
display: block !important;
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('declarations within a rule that uses @apply with !important remain not !important', () => {
|
|
const input = `
|
|
.foo {
|
|
@apply text-center !important;
|
|
float: left;
|
|
display: block !important;
|
|
}
|
|
`
|
|
|
|
const expected = `
|
|
.foo {
|
|
text-align: center !important;
|
|
float: left;
|
|
display: block !important;
|
|
}
|
|
`
|
|
|
|
expect.assertions(2)
|
|
|
|
return run(input).then((result) => {
|
|
expect(result.css).toMatchCss(expected)
|
|
expect(result.warnings().length).toBe(0)
|
|
})
|
|
})
|
|
|
|
test('lookup tree is correctly cached based on used tailwind atrules', async () => {
|
|
const input1 = `
|
|
@tailwind utilities;
|
|
|
|
.foo { @apply mt-4; }
|
|
`
|
|
|
|
const input2 = `
|
|
@tailwind components;
|
|
|
|
.foo { @apply mt-4; }
|
|
`
|
|
|
|
let config = {
|
|
corePlugins: [],
|
|
plugins: [
|
|
function ({ addUtilities, addComponents }) {
|
|
addUtilities({ '.mt-4': { marginTop: '1rem' } }, [])
|
|
addComponents({ '.container': { maxWidth: '500px' } }, [])
|
|
},
|
|
],
|
|
}
|
|
|
|
let output1 = await run(input1, config)
|
|
let output2 = await run(input2, config)
|
|
|
|
expect(output1.css).toMatchCss(`
|
|
.mt-4 { margin-top: 1rem; }
|
|
.foo { margin-top: 1rem; }
|
|
`)
|
|
|
|
expect(output2.css).toMatchCss(`
|
|
.container { max-width: 500px; }
|
|
.foo { margin-top: 1rem; }
|
|
`)
|
|
})
|