2486 lines
54 KiB
JavaScript
Executable File
2486 lines
54 KiB
JavaScript
Executable File
import _ from 'lodash'
|
|
import _postcss from 'postcss'
|
|
import tailwind from '../src/index'
|
|
import processPlugins from '../src/util/processPlugins'
|
|
import createPlugin from '../src/util/createPlugin'
|
|
|
|
function css(nodes) {
|
|
return _postcss.root({ nodes }).toString()
|
|
}
|
|
|
|
function makeConfig(overrides) {
|
|
return _.defaultsDeep(overrides, {
|
|
prefix: '',
|
|
important: false,
|
|
separator: ':',
|
|
})
|
|
}
|
|
|
|
test('plugins can create utilities with object syntax', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
function ({ addUtilities }) {
|
|
addUtilities({
|
|
'.object-fill': {
|
|
'object-fit': 'fill',
|
|
},
|
|
'.object-contain': {
|
|
'object-fit': 'contain',
|
|
},
|
|
'.object-cover': {
|
|
'object-fit': 'cover',
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(components.length).toBe(0)
|
|
expect(css(utilities)).toMatchCss(`
|
|
@layer utilities {
|
|
@variants {
|
|
.object-fill {
|
|
object-fit: fill
|
|
}
|
|
.object-contain {
|
|
object-fit: contain
|
|
}
|
|
.object-cover {
|
|
object-fit: cover
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can create utilities with arrays of objects', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
function ({ addUtilities }) {
|
|
addUtilities([
|
|
{
|
|
'.object-fill': {
|
|
'object-fit': 'fill',
|
|
},
|
|
},
|
|
{
|
|
'.object-contain': {
|
|
'object-fit': 'contain',
|
|
},
|
|
},
|
|
{
|
|
'.object-cover': {
|
|
'object-fit': 'cover',
|
|
},
|
|
},
|
|
])
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(components.length).toBe(0)
|
|
expect(css(utilities)).toMatchCss(`
|
|
@layer utilities {
|
|
@variants {
|
|
.object-fill {
|
|
object-fit: fill
|
|
}
|
|
.object-contain {
|
|
object-fit: contain
|
|
}
|
|
.object-cover {
|
|
object-fit: cover
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can create utilities with raw PostCSS nodes', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
function ({ addUtilities, postcss }) {
|
|
addUtilities([
|
|
postcss.rule({ selector: '.object-fill' }).append([
|
|
postcss.decl({
|
|
prop: 'object-fit',
|
|
value: 'fill',
|
|
}),
|
|
]),
|
|
postcss.rule({ selector: '.object-contain' }).append([
|
|
postcss.decl({
|
|
prop: 'object-fit',
|
|
value: 'contain',
|
|
}),
|
|
]),
|
|
postcss.rule({ selector: '.object-cover' }).append([
|
|
postcss.decl({
|
|
prop: 'object-fit',
|
|
value: 'cover',
|
|
}),
|
|
]),
|
|
])
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(components.length).toBe(0)
|
|
expect(css(utilities)).toMatchCss(`
|
|
@layer utilities {
|
|
@variants {
|
|
.object-fill {
|
|
object-fit: fill
|
|
}
|
|
.object-contain {
|
|
object-fit: contain
|
|
}
|
|
.object-cover {
|
|
object-fit: cover
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can create utilities with mixed object styles and PostCSS nodes', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
function ({ addUtilities, postcss }) {
|
|
addUtilities([
|
|
{
|
|
'.object-fill': {
|
|
objectFit: 'fill',
|
|
},
|
|
},
|
|
postcss.rule({ selector: '.object-contain' }).append([
|
|
postcss.decl({
|
|
prop: 'object-fit',
|
|
value: 'contain',
|
|
}),
|
|
]),
|
|
postcss.rule({ selector: '.object-cover' }).append([
|
|
postcss.decl({
|
|
prop: 'object-fit',
|
|
value: 'cover',
|
|
}),
|
|
]),
|
|
])
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(components.length).toBe(0)
|
|
expect(css(utilities)).toMatchCss(`
|
|
@layer utilities {
|
|
@variants {
|
|
.object-fill {
|
|
object-fit: fill
|
|
}
|
|
.object-contain {
|
|
object-fit: contain
|
|
}
|
|
.object-cover {
|
|
object-fit: cover
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can create utilities with variants', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
function ({ addUtilities }) {
|
|
addUtilities(
|
|
{
|
|
'.object-fill': {
|
|
'object-fit': 'fill',
|
|
},
|
|
'.object-contain': {
|
|
'object-fit': 'contain',
|
|
},
|
|
'.object-cover': {
|
|
'object-fit': 'cover',
|
|
},
|
|
},
|
|
['responsive', 'hover', 'group-hover', 'focus']
|
|
)
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(components.length).toBe(0)
|
|
expect(css(utilities)).toMatchCss(`
|
|
@layer utilities {
|
|
@variants responsive, hover, group-hover, focus {
|
|
.object-fill {
|
|
object-fit: fill
|
|
}
|
|
.object-contain {
|
|
object-fit: contain
|
|
}
|
|
.object-cover {
|
|
object-fit: cover
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can create components with object syntax', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
function ({ addComponents }) {
|
|
addComponents({
|
|
'.btn-blue': {
|
|
backgroundColor: 'blue',
|
|
color: 'white',
|
|
padding: '.5rem 1rem',
|
|
borderRadius: '.25rem',
|
|
},
|
|
'.btn-blue:hover': {
|
|
backgroundColor: 'darkblue',
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(utilities.length).toBe(0)
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.btn-blue {
|
|
background-color: blue;
|
|
color: white;
|
|
padding: .5rem 1rem;
|
|
border-radius: .25rem
|
|
}
|
|
.btn-blue:hover {
|
|
background-color: darkblue
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can add base styles with object syntax', () => {
|
|
const { base } = processPlugins(
|
|
[
|
|
function ({ addBase }) {
|
|
addBase({
|
|
img: {
|
|
maxWidth: '100%',
|
|
},
|
|
button: {
|
|
fontFamily: 'inherit',
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(css(base)).toMatchCss(`
|
|
@layer base {
|
|
img {
|
|
max-width: 100%
|
|
}
|
|
button {
|
|
font-family: inherit
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can add base styles with raw PostCSS nodes', () => {
|
|
const { base } = processPlugins(
|
|
[
|
|
function ({ addBase, postcss }) {
|
|
addBase([
|
|
postcss.rule({ selector: 'img' }).append([
|
|
postcss.decl({
|
|
prop: 'max-width',
|
|
value: '100%',
|
|
}),
|
|
]),
|
|
postcss.rule({ selector: 'button' }).append([
|
|
postcss.decl({
|
|
prop: 'font-family',
|
|
value: 'inherit',
|
|
}),
|
|
]),
|
|
])
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(css(base)).toMatchCss(`
|
|
@layer base {
|
|
img {
|
|
max-width: 100%
|
|
}
|
|
button {
|
|
font-family: inherit
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can create components with raw PostCSS nodes', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
function ({ addComponents, postcss }) {
|
|
addComponents([
|
|
postcss.rule({ selector: '.btn-blue' }).append([
|
|
postcss.decl({
|
|
prop: 'background-color',
|
|
value: 'blue',
|
|
}),
|
|
postcss.decl({
|
|
prop: 'color',
|
|
value: 'white',
|
|
}),
|
|
postcss.decl({
|
|
prop: 'padding',
|
|
value: '.5rem 1rem',
|
|
}),
|
|
postcss.decl({
|
|
prop: 'border-radius',
|
|
value: '.25rem',
|
|
}),
|
|
]),
|
|
postcss.rule({ selector: '.btn-blue:hover' }).append([
|
|
postcss.decl({
|
|
prop: 'background-color',
|
|
value: 'darkblue',
|
|
}),
|
|
]),
|
|
])
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(utilities.length).toBe(0)
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.btn-blue {
|
|
background-color: blue;
|
|
color: white;
|
|
padding: .5rem 1rem;
|
|
border-radius: .25rem
|
|
}
|
|
.btn-blue:hover {
|
|
background-color: darkblue
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can create components with mixed object styles and raw PostCSS nodes', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
function ({ addComponents, postcss }) {
|
|
addComponents([
|
|
postcss.rule({ selector: '.btn-blue' }).append([
|
|
postcss.decl({
|
|
prop: 'background-color',
|
|
value: 'blue',
|
|
}),
|
|
postcss.decl({
|
|
prop: 'color',
|
|
value: 'white',
|
|
}),
|
|
postcss.decl({
|
|
prop: 'padding',
|
|
value: '.5rem 1rem',
|
|
}),
|
|
postcss.decl({
|
|
prop: 'border-radius',
|
|
value: '.25rem',
|
|
}),
|
|
]),
|
|
{
|
|
'.btn-blue:hover': {
|
|
backgroundColor: 'darkblue',
|
|
},
|
|
},
|
|
])
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(utilities.length).toBe(0)
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.btn-blue {
|
|
background-color: blue;
|
|
color: white;
|
|
padding: .5rem 1rem;
|
|
border-radius: .25rem
|
|
}
|
|
.btn-blue:hover {
|
|
background-color: darkblue
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can create components with media queries with object syntax', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
function ({ addComponents }) {
|
|
addComponents({
|
|
'.container': {
|
|
width: '100%',
|
|
},
|
|
'@media (min-width: 100px)': {
|
|
'.container': {
|
|
maxWidth: '100px',
|
|
},
|
|
},
|
|
'@media (min-width: 200px)': {
|
|
'.container': {
|
|
maxWidth: '200px',
|
|
},
|
|
},
|
|
'@media (min-width: 300px)': {
|
|
'.container': {
|
|
maxWidth: '300px',
|
|
},
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(utilities.length).toBe(0)
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.container {
|
|
width: 100%
|
|
}
|
|
@media (min-width: 100px) {
|
|
.container {
|
|
max-width: 100px
|
|
}
|
|
}
|
|
@media (min-width: 200px) {
|
|
.container {
|
|
max-width: 200px
|
|
}
|
|
}
|
|
@media (min-width: 300px) {
|
|
.container {
|
|
max-width: 300px
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('media queries can be defined multiple times using objects-in-array syntax', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
function ({ addComponents }) {
|
|
addComponents([
|
|
{
|
|
'.container': {
|
|
width: '100%',
|
|
},
|
|
'@media (min-width: 100px)': {
|
|
'.container': {
|
|
maxWidth: '100px',
|
|
},
|
|
},
|
|
},
|
|
{
|
|
'.btn': {
|
|
padding: '1rem .5rem',
|
|
display: 'block',
|
|
},
|
|
'@media (min-width: 100px)': {
|
|
'.btn': {
|
|
display: 'inline-block',
|
|
},
|
|
},
|
|
},
|
|
])
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(utilities.length).toBe(0)
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.container {
|
|
width: 100%
|
|
}
|
|
@media (min-width: 100px) {
|
|
.container {
|
|
max-width: 100px
|
|
}
|
|
}
|
|
.btn {
|
|
padding: 1rem .5rem;
|
|
display: block
|
|
}
|
|
@media (min-width: 100px) {
|
|
.btn {
|
|
display: inline-block
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can create nested rules', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
function ({ addComponents }) {
|
|
addComponents({
|
|
'.btn-blue': {
|
|
backgroundColor: 'blue',
|
|
color: 'white',
|
|
padding: '.5rem 1rem',
|
|
borderRadius: '.25rem',
|
|
'&:hover': {
|
|
backgroundColor: 'darkblue',
|
|
},
|
|
'@media (min-width: 500px)': {
|
|
'&:hover': {
|
|
backgroundColor: 'orange',
|
|
},
|
|
},
|
|
'> a': {
|
|
color: 'red',
|
|
},
|
|
'h1 &': {
|
|
color: 'purple',
|
|
},
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(utilities.length).toBe(0)
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.btn-blue {
|
|
background-color: blue;
|
|
color: white;
|
|
padding: .5rem 1rem;
|
|
border-radius: .25rem;
|
|
}
|
|
.btn-blue:hover {
|
|
background-color: darkblue;
|
|
}
|
|
@media (min-width: 500px) {
|
|
.btn-blue:hover {
|
|
background-color: orange;
|
|
}
|
|
}
|
|
.btn-blue > a {
|
|
color: red;
|
|
}
|
|
h1 .btn-blue {
|
|
color: purple;
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can create rules with escaped selectors', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
function ({ e, addUtilities }) {
|
|
addUtilities({
|
|
[`.${e('top-1/4')}`]: {
|
|
top: '25%',
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(components.length).toBe(0)
|
|
expect(css(utilities)).toMatchCss(`
|
|
@layer utilities {
|
|
@variants {
|
|
.top-1\\/4 {
|
|
top: 25%
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can access the current config', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
function ({ addComponents, config }) {
|
|
const containerClasses = [
|
|
{
|
|
'.container': {
|
|
width: '100%',
|
|
},
|
|
},
|
|
]
|
|
|
|
_.forEach(config('screens'), (breakpoint) => {
|
|
containerClasses.push({
|
|
[`@media (min-width: ${breakpoint})`]: {
|
|
'.container': { maxWidth: breakpoint },
|
|
},
|
|
})
|
|
})
|
|
addComponents(containerClasses)
|
|
},
|
|
],
|
|
makeConfig({
|
|
screens: {
|
|
sm: '576px',
|
|
md: '768px',
|
|
lg: '992px',
|
|
xl: '1200px',
|
|
},
|
|
})
|
|
)
|
|
|
|
expect(utilities.length).toBe(0)
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.container {
|
|
width: 100%
|
|
}
|
|
@media (min-width: 576px) {
|
|
.container {
|
|
max-width: 576px
|
|
}
|
|
}
|
|
@media (min-width: 768px) {
|
|
.container {
|
|
max-width: 768px
|
|
}
|
|
}
|
|
@media (min-width: 992px) {
|
|
.container {
|
|
max-width: 992px
|
|
}
|
|
}
|
|
@media (min-width: 1200px) {
|
|
.container {
|
|
max-width: 1200px
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can access the variants config directly', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
function ({ addUtilities, variants }) {
|
|
addUtilities(
|
|
{
|
|
'.object-fill': {
|
|
'object-fit': 'fill',
|
|
},
|
|
'.object-contain': {
|
|
'object-fit': 'contain',
|
|
},
|
|
'.object-cover': {
|
|
'object-fit': 'cover',
|
|
},
|
|
},
|
|
variants('objectFit')
|
|
)
|
|
},
|
|
],
|
|
makeConfig({
|
|
variants: {
|
|
objectFit: ['responsive', 'focus', 'hover'],
|
|
},
|
|
})
|
|
)
|
|
|
|
expect(components.length).toBe(0)
|
|
expect(css(utilities)).toMatchCss(`
|
|
@layer utilities {
|
|
@variants responsive, focus, hover {
|
|
.object-fill {
|
|
object-fit: fill
|
|
}
|
|
.object-contain {
|
|
object-fit: contain
|
|
}
|
|
.object-cover {
|
|
object-fit: cover
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins apply all global variants when variants are configured globally', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
function ({ addUtilities, variants }) {
|
|
addUtilities(
|
|
{
|
|
'.object-fill': {
|
|
'object-fit': 'fill',
|
|
},
|
|
},
|
|
variants('objectFit')
|
|
)
|
|
addUtilities(
|
|
{
|
|
'.rotate-90deg': {
|
|
transform: 'rotate(90deg)',
|
|
},
|
|
},
|
|
variants('rotate')
|
|
)
|
|
},
|
|
],
|
|
makeConfig({
|
|
variants: ['responsive', 'focus', 'hover'],
|
|
})
|
|
)
|
|
|
|
expect(components.length).toBe(0)
|
|
expect(css(utilities)).toMatchCss(`
|
|
@layer utilities {
|
|
@variants responsive, focus, hover {
|
|
.object-fill {
|
|
object-fit: fill
|
|
}
|
|
}
|
|
}
|
|
@layer utilities {
|
|
@variants responsive, focus, hover {
|
|
.rotate-90deg {
|
|
transform: rotate(90deg)
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can check if corePlugins are enabled', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
function ({ addUtilities, corePlugins }) {
|
|
addUtilities({
|
|
'.test': {
|
|
'text-color': corePlugins('textColor') ? 'true' : 'false',
|
|
opacity: corePlugins('opacity') ? 'true' : 'false',
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig({
|
|
corePlugins: { textColor: false },
|
|
})
|
|
)
|
|
|
|
expect(components.length).toBe(0)
|
|
expect(css(utilities)).toMatchCss(`
|
|
@layer utilities {
|
|
@variants {
|
|
.test {
|
|
text-color: false;
|
|
opacity: true
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can check if corePlugins are enabled when using array white-listing', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
function ({ addUtilities, corePlugins }) {
|
|
addUtilities({
|
|
'.test': {
|
|
'text-color': corePlugins('textColor') ? 'true' : 'false',
|
|
opacity: corePlugins('opacity') ? 'true' : 'false',
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig({
|
|
corePlugins: ['textColor'],
|
|
})
|
|
)
|
|
|
|
expect(components.length).toBe(0)
|
|
expect(css(utilities)).toMatchCss(`
|
|
@layer utilities {
|
|
@variants {
|
|
.test {
|
|
text-color: true;
|
|
opacity: false
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can provide fallbacks to keys missing from the config', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
function ({ addComponents, config }) {
|
|
addComponents({
|
|
'.btn': {
|
|
borderRadius: config('borderRadius.default', '.25rem'),
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig({
|
|
borderRadius: {
|
|
1: '1px',
|
|
2: '2px',
|
|
4: '4px',
|
|
8: '8px',
|
|
},
|
|
})
|
|
)
|
|
|
|
expect(utilities.length).toBe(0)
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.btn {
|
|
border-radius: .25rem
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('variants are optional when adding utilities', () => {
|
|
const { utilities } = processPlugins(
|
|
[
|
|
function ({ addUtilities }) {
|
|
addUtilities({
|
|
'.border-collapse': {
|
|
'border-collapse': 'collapse',
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(css(utilities)).toMatchCss(`
|
|
@layer utilities {
|
|
@variants {
|
|
.border-collapse {
|
|
border-collapse: collapse
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can add multiple sets of utilities and components', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
function ({ addUtilities, addComponents }) {
|
|
addComponents({
|
|
'.card': {
|
|
padding: '1rem',
|
|
borderRadius: '.25rem',
|
|
},
|
|
})
|
|
|
|
addUtilities({
|
|
'.skew-12deg': {
|
|
transform: 'skewY(-12deg)',
|
|
},
|
|
})
|
|
|
|
addComponents({
|
|
'.btn': {
|
|
padding: '1rem .5rem',
|
|
display: 'inline-block',
|
|
},
|
|
})
|
|
|
|
addUtilities({
|
|
'.border-collapse': {
|
|
borderCollapse: 'collapse',
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(css(utilities)).toMatchCss(`
|
|
@layer utilities {
|
|
@variants {
|
|
.skew-12deg {
|
|
transform: skewY(-12deg)
|
|
}
|
|
}
|
|
}
|
|
@layer utilities {
|
|
@variants {
|
|
.border-collapse {
|
|
border-collapse: collapse
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.card {
|
|
padding: 1rem;
|
|
border-radius: .25rem
|
|
}
|
|
}
|
|
}
|
|
@layer components {
|
|
@variants {
|
|
.btn {
|
|
padding: 1rem .5rem;
|
|
display: inline-block
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins respect prefix and important options by default when adding utilities', () => {
|
|
return _postcss([
|
|
tailwind({
|
|
prefix: 'tw-',
|
|
important: true,
|
|
corePlugins: [],
|
|
plugins: [
|
|
function ({ addUtilities }) {
|
|
addUtilities({
|
|
'.rotate-90': {
|
|
transform: 'rotate(90deg)',
|
|
},
|
|
})
|
|
},
|
|
],
|
|
}),
|
|
])
|
|
.process(
|
|
`
|
|
@tailwind utilities;
|
|
`,
|
|
{ from: undefined }
|
|
)
|
|
.then((result) => {
|
|
const expected = `
|
|
.tw-rotate-90 {
|
|
transform: rotate(90deg) !important
|
|
}
|
|
`
|
|
|
|
expect(result.css).toMatchCss(expected)
|
|
})
|
|
})
|
|
|
|
test('when important is a selector it is used to scope utilities instead of adding !important', () => {
|
|
return _postcss([
|
|
tailwind({
|
|
prefix: 'tw-',
|
|
important: '#app',
|
|
corePlugins: [],
|
|
plugins: [
|
|
function ({ addUtilities }) {
|
|
addUtilities({
|
|
'.rotate-90': {
|
|
transform: 'rotate(90deg)',
|
|
},
|
|
})
|
|
},
|
|
],
|
|
}),
|
|
])
|
|
.process(
|
|
`
|
|
@tailwind utilities;
|
|
`,
|
|
{ from: undefined }
|
|
)
|
|
.then((result) => {
|
|
const expected = `
|
|
#app .tw-rotate-90 {
|
|
transform: rotate(90deg)
|
|
}
|
|
`
|
|
|
|
expect(result.css).toMatchCss(expected)
|
|
})
|
|
})
|
|
|
|
test('when important is a selector it scopes all selectors in a rule, even though defining utilities like this is stupid', () => {
|
|
return _postcss([
|
|
tailwind({
|
|
important: '#app',
|
|
corePlugins: [],
|
|
plugins: [
|
|
function ({ addUtilities }) {
|
|
addUtilities({
|
|
'.rotate-90, .rotate-1\\/4': {
|
|
transform: 'rotate(90deg)',
|
|
},
|
|
})
|
|
},
|
|
],
|
|
}),
|
|
])
|
|
.process(
|
|
`
|
|
@tailwind utilities;
|
|
`,
|
|
{ from: undefined }
|
|
)
|
|
.then((result) => {
|
|
const expected = `
|
|
#app .rotate-90, #app .rotate-1\\/4 {
|
|
transform: rotate(90deg)
|
|
}
|
|
`
|
|
|
|
expect(result.css).toMatchCss(expected)
|
|
})
|
|
})
|
|
|
|
test('important utilities are not made double important when important option is used', () => {
|
|
return _postcss([
|
|
tailwind({
|
|
important: true,
|
|
corePlugins: [],
|
|
plugins: [
|
|
function ({ addUtilities }) {
|
|
addUtilities({
|
|
'.rotate-90': {
|
|
transform: 'rotate(90deg) !important',
|
|
},
|
|
})
|
|
},
|
|
],
|
|
}),
|
|
])
|
|
.process(
|
|
`
|
|
@tailwind utilities;
|
|
`,
|
|
{ from: undefined }
|
|
)
|
|
.then((result) => {
|
|
const expected = `
|
|
.rotate-90 {
|
|
transform: rotate(90deg) !important
|
|
}
|
|
`
|
|
|
|
expect(result.css).toMatchCss(expected)
|
|
})
|
|
})
|
|
|
|
test("component declarations respect the 'prefix' option by default", () => {
|
|
const { components } = processPlugins(
|
|
[
|
|
function ({ addComponents }) {
|
|
addComponents({
|
|
'.btn-blue': {
|
|
backgroundColor: 'blue',
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig({
|
|
prefix: 'tw-',
|
|
})
|
|
)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.tw-btn-blue {
|
|
background-color: blue
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('all selectors in a rule are prefixed', () => {
|
|
const { utilities, components } = processPlugins(
|
|
[
|
|
function ({ addUtilities, addComponents }) {
|
|
addUtilities({
|
|
'.rotate-90, .rotate-1\\/4': {
|
|
transform: 'rotate(90deg)',
|
|
},
|
|
})
|
|
addComponents({
|
|
'.btn-blue, .btn-red': {
|
|
padding: '10px',
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig({
|
|
prefix: 'tw-',
|
|
})
|
|
)
|
|
|
|
expect(css(utilities)).toMatchCss(`
|
|
@layer utilities {
|
|
@variants {
|
|
.tw-rotate-90, .tw-rotate-1\\/4 {
|
|
transform: rotate(90deg)
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.tw-btn-blue, .tw-btn-red {
|
|
padding: 10px
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test("component declarations can optionally ignore 'prefix' option", () => {
|
|
const { components } = processPlugins(
|
|
[
|
|
function ({ addComponents }) {
|
|
addComponents(
|
|
{
|
|
'.btn-blue': {
|
|
backgroundColor: 'blue',
|
|
},
|
|
},
|
|
{ respectPrefix: false }
|
|
)
|
|
},
|
|
],
|
|
makeConfig({
|
|
prefix: 'tw-',
|
|
})
|
|
)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.btn-blue {
|
|
background-color: blue
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can create components with variants', () => {
|
|
const { components } = processPlugins(
|
|
[
|
|
function ({ addComponents }) {
|
|
addComponents(
|
|
{
|
|
'.btn-blue': {
|
|
backgroundColor: 'blue',
|
|
},
|
|
},
|
|
{ variants: ['responsive'] }
|
|
)
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants responsive {
|
|
.btn-blue {
|
|
background-color: blue
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can use the array shorthand to add variants to components', () => {
|
|
const { components } = processPlugins(
|
|
[
|
|
function ({ addComponents }) {
|
|
addComponents(
|
|
{
|
|
'.btn-blue': {
|
|
backgroundColor: 'blue',
|
|
},
|
|
},
|
|
['responsive', 'hover']
|
|
)
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants responsive, hover {
|
|
.btn-blue {
|
|
background-color: blue
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('components that add variants manually do not add an extra variants wrapper', () => {
|
|
const { components } = processPlugins(
|
|
[
|
|
function ({ addComponents }) {
|
|
addComponents({
|
|
'@variants responsive': {
|
|
'.btn-blue': {
|
|
backgroundColor: 'blue',
|
|
},
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants responsive {
|
|
.btn-blue {
|
|
background-color: blue
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test("component declarations are not affected by the 'important' option", () => {
|
|
const { components } = processPlugins(
|
|
[
|
|
function ({ addComponents }) {
|
|
addComponents({
|
|
'.btn-blue': {
|
|
backgroundColor: 'blue',
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig({
|
|
important: true,
|
|
})
|
|
)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.btn-blue {
|
|
background-color: blue
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test("plugins can apply the user's chosen prefix to components manually", () => {
|
|
const { components } = processPlugins(
|
|
[
|
|
function ({ addComponents, prefix }) {
|
|
addComponents(
|
|
{
|
|
[prefix('.btn-blue')]: {
|
|
backgroundColor: 'blue',
|
|
},
|
|
},
|
|
{ respectPrefix: false }
|
|
)
|
|
},
|
|
],
|
|
makeConfig({
|
|
prefix: 'tw-',
|
|
})
|
|
)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.tw-btn-blue {
|
|
background-color: blue
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('prefix can optionally be ignored for utilities', () => {
|
|
return _postcss([
|
|
tailwind({
|
|
prefix: 'tw-',
|
|
corePlugins: [],
|
|
plugins: [
|
|
function ({ addUtilities }) {
|
|
addUtilities(
|
|
{
|
|
'.rotate-90': {
|
|
transform: 'rotate(90deg)',
|
|
},
|
|
},
|
|
{
|
|
respectPrefix: false,
|
|
}
|
|
)
|
|
},
|
|
],
|
|
}),
|
|
])
|
|
.process(
|
|
`
|
|
@tailwind utilities;
|
|
`,
|
|
{ from: undefined }
|
|
)
|
|
.then((result) => {
|
|
const expected = `
|
|
.rotate-90 {
|
|
transform: rotate(90deg)
|
|
}
|
|
`
|
|
|
|
expect(result.css).toMatchCss(expected)
|
|
})
|
|
})
|
|
|
|
test('important can optionally be ignored for utilities', () => {
|
|
return _postcss([
|
|
tailwind({
|
|
important: true,
|
|
corePlugins: [],
|
|
plugins: [
|
|
function ({ addUtilities }) {
|
|
addUtilities(
|
|
{
|
|
'.rotate-90': {
|
|
transform: 'rotate(90deg)',
|
|
},
|
|
},
|
|
{
|
|
respectImportant: false,
|
|
}
|
|
)
|
|
},
|
|
],
|
|
}),
|
|
])
|
|
.process(
|
|
`
|
|
@tailwind utilities;
|
|
`,
|
|
{ from: undefined }
|
|
)
|
|
.then((result) => {
|
|
const expected = `
|
|
.rotate-90 {
|
|
transform: rotate(90deg)
|
|
}
|
|
`
|
|
|
|
expect(result.css).toMatchCss(expected)
|
|
})
|
|
})
|
|
|
|
test('variants can still be specified when ignoring prefix and important options', () => {
|
|
const { utilities } = processPlugins(
|
|
[
|
|
function ({ addUtilities }) {
|
|
addUtilities(
|
|
{
|
|
'.rotate-90': {
|
|
transform: 'rotate(90deg)',
|
|
},
|
|
},
|
|
{
|
|
variants: ['responsive', 'hover', 'focus'],
|
|
respectImportant: false,
|
|
respectPrefix: false,
|
|
}
|
|
)
|
|
},
|
|
],
|
|
makeConfig({
|
|
prefix: 'tw-',
|
|
important: true,
|
|
})
|
|
)
|
|
|
|
expect(css(utilities)).toMatchCss(`
|
|
@layer utilities {
|
|
@variants responsive, hover, focus {
|
|
.rotate-90 {
|
|
transform: rotate(90deg)
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('prefix will prefix all classes in a selector', () => {
|
|
const { components } = processPlugins(
|
|
[
|
|
function ({ addComponents, prefix }) {
|
|
addComponents(
|
|
{
|
|
[prefix('.btn-blue .w-1\\/4 > h1.text-xl + a .bar')]: {
|
|
backgroundColor: 'blue',
|
|
},
|
|
},
|
|
{ respectPrefix: false }
|
|
)
|
|
},
|
|
],
|
|
makeConfig({
|
|
prefix: 'tw-',
|
|
})
|
|
)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.tw-btn-blue .tw-w-1\\/4 > h1.tw-text-xl + a .tw-bar {
|
|
background-color: blue
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can be provided as an object with a handler function', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
{
|
|
handler({ addUtilities }) {
|
|
addUtilities({
|
|
'.object-fill': {
|
|
'object-fit': 'fill',
|
|
},
|
|
'.object-contain': {
|
|
'object-fit': 'contain',
|
|
},
|
|
'.object-cover': {
|
|
'object-fit': 'cover',
|
|
},
|
|
})
|
|
},
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(components.length).toBe(0)
|
|
expect(css(utilities)).toMatchCss(`
|
|
@layer utilities {
|
|
@variants {
|
|
.object-fill {
|
|
object-fit: fill
|
|
}
|
|
.object-contain {
|
|
object-fit: contain
|
|
}
|
|
.object-cover {
|
|
object-fit: cover
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can provide a config but no handler', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
{
|
|
config: {
|
|
prefix: 'tw-',
|
|
},
|
|
},
|
|
{
|
|
handler({ addUtilities }) {
|
|
addUtilities({
|
|
'.object-fill': {
|
|
'object-fit': 'fill',
|
|
},
|
|
'.object-contain': {
|
|
'object-fit': 'contain',
|
|
},
|
|
'.object-cover': {
|
|
'object-fit': 'cover',
|
|
},
|
|
})
|
|
},
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(components.length).toBe(0)
|
|
expect(css(utilities)).toMatchCss(`
|
|
@layer utilities {
|
|
@variants {
|
|
.object-fill {
|
|
object-fit: fill
|
|
}
|
|
.object-contain {
|
|
object-fit: contain
|
|
}
|
|
.object-cover {
|
|
object-fit: cover
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('plugins can be created using the `createPlugin` function', () => {
|
|
const plugin = createPlugin(
|
|
function ({ addUtilities, theme, variants }) {
|
|
const utilities = _.fromPairs(
|
|
_.toPairs(theme('testPlugin')).map(([k, v]) => [`.test-${k}`, { testProperty: v }])
|
|
)
|
|
|
|
addUtilities(utilities, variants('testPlugin'))
|
|
},
|
|
{
|
|
theme: {
|
|
testPlugin: {
|
|
sm: '1rem',
|
|
md: '2rem',
|
|
lg: '3rem',
|
|
},
|
|
},
|
|
variants: {
|
|
testPlugin: ['responsive', 'hover'],
|
|
},
|
|
}
|
|
)
|
|
|
|
return _postcss([
|
|
tailwind({
|
|
corePlugins: [],
|
|
theme: {
|
|
screens: {
|
|
sm: '400px',
|
|
},
|
|
},
|
|
plugins: [plugin],
|
|
}),
|
|
])
|
|
.process(
|
|
`
|
|
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
`,
|
|
{ from: undefined }
|
|
)
|
|
.then((result) => {
|
|
const expected = `
|
|
.test-sm {
|
|
test-property: 1rem
|
|
}
|
|
.test-md {
|
|
test-property: 2rem
|
|
}
|
|
.test-lg {
|
|
test-property: 3rem
|
|
}
|
|
.hover\\:test-sm:hover {
|
|
test-property: 1rem
|
|
}
|
|
.hover\\:test-md:hover {
|
|
test-property: 2rem
|
|
}
|
|
.hover\\:test-lg:hover {
|
|
test-property: 3rem
|
|
}
|
|
|
|
@media (min-width: 400px) {
|
|
.sm\\:test-sm {
|
|
test-property: 1rem
|
|
}
|
|
.sm\\:test-md {
|
|
test-property: 2rem
|
|
}
|
|
.sm\\:test-lg {
|
|
test-property: 3rem
|
|
}
|
|
.sm\\:hover\\:test-sm:hover {
|
|
test-property: 1rem
|
|
}
|
|
.sm\\:hover\\:test-md:hover {
|
|
test-property: 2rem
|
|
}
|
|
.sm\\:hover\\:test-lg:hover {
|
|
test-property: 3rem
|
|
}
|
|
}
|
|
`
|
|
|
|
expect(result.css).toMatchCss(expected)
|
|
})
|
|
})
|
|
|
|
test('plugins with extra options can be created using the `createPlugin.withOptions` function', () => {
|
|
const plugin = createPlugin.withOptions(
|
|
function ({ className }) {
|
|
return function ({ addUtilities, theme, variants }) {
|
|
const utilities = _.fromPairs(
|
|
_.toPairs(theme('testPlugin')).map(([k, v]) => [
|
|
`.${className}-${k}`,
|
|
{ testProperty: v },
|
|
])
|
|
)
|
|
|
|
addUtilities(utilities, variants('testPlugin'))
|
|
}
|
|
},
|
|
function () {
|
|
return {
|
|
theme: {
|
|
testPlugin: {
|
|
sm: '1rem',
|
|
md: '2rem',
|
|
lg: '3rem',
|
|
},
|
|
},
|
|
variants: {
|
|
testPlugin: ['responsive', 'hover'],
|
|
},
|
|
}
|
|
}
|
|
)
|
|
|
|
return _postcss([
|
|
tailwind({
|
|
corePlugins: [],
|
|
theme: {
|
|
screens: {
|
|
sm: '400px',
|
|
},
|
|
},
|
|
plugins: [plugin({ className: 'banana' })],
|
|
}),
|
|
])
|
|
.process(
|
|
`
|
|
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
`,
|
|
{ from: undefined }
|
|
)
|
|
.then((result) => {
|
|
const expected = `
|
|
.banana-sm {
|
|
test-property: 1rem
|
|
}
|
|
.banana-md {
|
|
test-property: 2rem
|
|
}
|
|
.banana-lg {
|
|
test-property: 3rem
|
|
}
|
|
.hover\\:banana-sm:hover {
|
|
test-property: 1rem
|
|
}
|
|
.hover\\:banana-md:hover {
|
|
test-property: 2rem
|
|
}
|
|
.hover\\:banana-lg:hover {
|
|
test-property: 3rem
|
|
}
|
|
|
|
@media (min-width: 400px) {
|
|
.sm\\:banana-sm {
|
|
test-property: 1rem
|
|
}
|
|
.sm\\:banana-md {
|
|
test-property: 2rem
|
|
}
|
|
.sm\\:banana-lg {
|
|
test-property: 3rem
|
|
}
|
|
.sm\\:hover\\:banana-sm:hover {
|
|
test-property: 1rem
|
|
}
|
|
.sm\\:hover\\:banana-md:hover {
|
|
test-property: 2rem
|
|
}
|
|
.sm\\:hover\\:banana-lg:hover {
|
|
test-property: 3rem
|
|
}
|
|
}
|
|
`
|
|
|
|
expect(result.css).toMatchCss(expected)
|
|
})
|
|
})
|
|
|
|
test('plugins should cache correctly', () => {
|
|
const plugin = createPlugin.withOptions(
|
|
({ className = 'banana' } = {}) => ({ addComponents, variants }) => {
|
|
addComponents({ [`.${className}`]: { position: 'absolute' } }, variants('testPlugin'))
|
|
},
|
|
() => ({ variants: { testPlugin: ['responsive'] } })
|
|
)
|
|
|
|
function run(options = {}) {
|
|
return _postcss([
|
|
tailwind({
|
|
corePlugins: [],
|
|
theme: { screens: { sm: '400px' } },
|
|
plugins: [plugin(options)],
|
|
}),
|
|
]).process(`@tailwind base; @tailwind components; @tailwind utilities;`, {
|
|
from: undefined,
|
|
})
|
|
}
|
|
|
|
return Promise.all([run(), run({ className: 'apple' })]).then(([result1, result2]) => {
|
|
const expected1 = `
|
|
.banana {
|
|
position: absolute;
|
|
}
|
|
|
|
@media (min-width: 400px) {
|
|
.sm\\:banana {
|
|
position: absolute;
|
|
}
|
|
}
|
|
`
|
|
|
|
const expected2 = `
|
|
.apple {
|
|
position: absolute;
|
|
}
|
|
|
|
@media (min-width: 400px) {
|
|
.sm\\:apple {
|
|
position: absolute;
|
|
}
|
|
}
|
|
`
|
|
expect(result1.css).toMatchCss(expected1)
|
|
expect(result2.css).toMatchCss(expected2)
|
|
})
|
|
})
|
|
|
|
test('plugins created using `createPlugin.withOptions` do not need to be invoked if the user wants to use the default options', () => {
|
|
const plugin = createPlugin.withOptions(
|
|
function ({ className } = { className: 'banana' }) {
|
|
return function ({ addUtilities, theme, variants }) {
|
|
const utilities = _.fromPairs(
|
|
_.toPairs(theme('testPlugin')).map(([k, v]) => [
|
|
`.${className}-${k}`,
|
|
{ testProperty: v },
|
|
])
|
|
)
|
|
|
|
addUtilities(utilities, variants('testPlugin'))
|
|
}
|
|
},
|
|
function () {
|
|
return {
|
|
theme: {
|
|
testPlugin: {
|
|
sm: '1rem',
|
|
md: '2rem',
|
|
lg: '3rem',
|
|
},
|
|
},
|
|
variants: {
|
|
testPlugin: ['responsive', 'hover'],
|
|
},
|
|
}
|
|
}
|
|
)
|
|
|
|
return _postcss([
|
|
tailwind({
|
|
corePlugins: [],
|
|
theme: {
|
|
screens: {
|
|
sm: '400px',
|
|
},
|
|
},
|
|
plugins: [plugin],
|
|
}),
|
|
])
|
|
.process(
|
|
`
|
|
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
`,
|
|
{ from: undefined }
|
|
)
|
|
.then((result) => {
|
|
const expected = `
|
|
.banana-sm {
|
|
test-property: 1rem
|
|
}
|
|
.banana-md {
|
|
test-property: 2rem
|
|
}
|
|
.banana-lg {
|
|
test-property: 3rem
|
|
}
|
|
.hover\\:banana-sm:hover {
|
|
test-property: 1rem
|
|
}
|
|
.hover\\:banana-md:hover {
|
|
test-property: 2rem
|
|
}
|
|
.hover\\:banana-lg:hover {
|
|
test-property: 3rem
|
|
}
|
|
|
|
@media (min-width: 400px) {
|
|
.sm\\:banana-sm {
|
|
test-property: 1rem
|
|
}
|
|
.sm\\:banana-md {
|
|
test-property: 2rem
|
|
}
|
|
.sm\\:banana-lg {
|
|
test-property: 3rem
|
|
}
|
|
.sm\\:hover\\:banana-sm:hover {
|
|
test-property: 1rem
|
|
}
|
|
.sm\\:hover\\:banana-md:hover {
|
|
test-property: 2rem
|
|
}
|
|
.sm\\:hover\\:banana-lg:hover {
|
|
test-property: 3rem
|
|
}
|
|
}
|
|
`
|
|
|
|
expect(result.css).toMatchCss(expected)
|
|
})
|
|
})
|
|
|
|
test('the configFunction parameter is optional when using the `createPlugin.withOptions` function', () => {
|
|
const plugin = createPlugin.withOptions(function ({ className }) {
|
|
return function ({ addUtilities, theme, variants }) {
|
|
const utilities = _.fromPairs(
|
|
_.toPairs(theme('testPlugin')).map(([k, v]) => [`.${className}-${k}`, { testProperty: v }])
|
|
)
|
|
|
|
addUtilities(utilities, variants('testPlugin'))
|
|
}
|
|
})
|
|
|
|
return _postcss([
|
|
tailwind({
|
|
corePlugins: [],
|
|
theme: {
|
|
screens: {
|
|
sm: '400px',
|
|
},
|
|
testPlugin: {
|
|
sm: '1px',
|
|
md: '2px',
|
|
lg: '3px',
|
|
},
|
|
},
|
|
variants: {
|
|
testPlugin: ['responsive', 'focus'],
|
|
},
|
|
plugins: [plugin({ className: 'banana' })],
|
|
}),
|
|
])
|
|
.process(
|
|
`
|
|
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
`,
|
|
{ from: undefined }
|
|
)
|
|
.then((result) => {
|
|
const expected = `
|
|
.banana-sm {
|
|
test-property: 1px
|
|
}
|
|
.banana-md {
|
|
test-property: 2px
|
|
}
|
|
.banana-lg {
|
|
test-property: 3px
|
|
}
|
|
.focus\\:banana-sm:focus {
|
|
test-property: 1px
|
|
}
|
|
.focus\\:banana-md:focus {
|
|
test-property: 2px
|
|
}
|
|
.focus\\:banana-lg:focus {
|
|
test-property: 3px
|
|
}
|
|
|
|
@media (min-width: 400px) {
|
|
.sm\\:banana-sm {
|
|
test-property: 1px
|
|
}
|
|
.sm\\:banana-md {
|
|
test-property: 2px
|
|
}
|
|
.sm\\:banana-lg {
|
|
test-property: 3px
|
|
}
|
|
.sm\\:focus\\:banana-sm:focus {
|
|
test-property: 1px
|
|
}
|
|
.sm\\:focus\\:banana-md:focus {
|
|
test-property: 2px
|
|
}
|
|
.sm\\:focus\\:banana-lg:focus {
|
|
test-property: 3px
|
|
}
|
|
}
|
|
`
|
|
|
|
expect(result.css).toMatchCss(expected)
|
|
})
|
|
})
|
|
|
|
test('plugins can extend variants', () => {
|
|
const plugin = createPlugin(function () {}, {
|
|
variants: {
|
|
opacity: ({ before }) => before(['active'], 'focus'),
|
|
},
|
|
})
|
|
|
|
return _postcss([
|
|
tailwind({
|
|
theme: {
|
|
opacity: { 0: '0', 100: '1' },
|
|
},
|
|
corePlugins: ['opacity'],
|
|
variants: { opacity: ({ without }) => without(['responsive']) },
|
|
plugins: [plugin],
|
|
}),
|
|
])
|
|
.process(
|
|
`
|
|
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
`,
|
|
{ from: undefined }
|
|
)
|
|
.then((result) => {
|
|
const expected = `
|
|
.opacity-0 {
|
|
opacity: 0
|
|
}
|
|
.opacity-100 {
|
|
opacity: 1
|
|
}
|
|
.group:hover .group-hover\\:opacity-0 {
|
|
opacity: 0
|
|
}
|
|
.group:hover .group-hover\\:opacity-100 {
|
|
opacity: 1
|
|
}
|
|
.focus-within\\:opacity-0:focus-within {
|
|
opacity: 0
|
|
}
|
|
.focus-within\\:opacity-100:focus-within {
|
|
opacity: 1
|
|
}
|
|
.hover\\:opacity-0:hover {
|
|
opacity: 0
|
|
}
|
|
.hover\\:opacity-100:hover {
|
|
opacity: 1
|
|
}
|
|
.active\\:opacity-0:active {
|
|
opacity: 0
|
|
}
|
|
.active\\:opacity-100:active {
|
|
opacity: 1
|
|
}
|
|
.focus\\:opacity-0:focus {
|
|
opacity: 0
|
|
}
|
|
.focus\\:opacity-100:focus {
|
|
opacity: 1
|
|
}
|
|
`
|
|
|
|
expect(result.css).toMatchCss(expected)
|
|
})
|
|
})
|
|
|
|
test('keyframes are not escaped', () => {
|
|
const { components, utilities } = processPlugins(
|
|
[
|
|
function ({ addUtilities, addComponents }) {
|
|
addComponents({
|
|
'@keyframes foo': {
|
|
'25.001%': {
|
|
color: 'black',
|
|
},
|
|
},
|
|
})
|
|
addUtilities({
|
|
'@keyframes bar': {
|
|
'75.001%': {
|
|
color: 'white',
|
|
},
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig()
|
|
)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
@keyframes foo {
|
|
25.001% {
|
|
color: black
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
expect(css(utilities)).toMatchCss(`
|
|
@layer utilities {
|
|
@variants {
|
|
@keyframes bar {
|
|
75.001% {
|
|
color: white
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('font sizes are retrieved without default line-heights or letter-spacing using theme function', () => {
|
|
const { components } = processPlugins(
|
|
[
|
|
function ({ addComponents, theme }) {
|
|
addComponents({
|
|
'.foo': {
|
|
fontSize: theme('fontSize.sm'),
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig({
|
|
theme: {
|
|
fontSize: {
|
|
sm: ['14px', '20px'],
|
|
},
|
|
},
|
|
})
|
|
)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.foo {
|
|
font-size: 14px;
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('outlines are retrieved without outline-offset using theme function', () => {
|
|
const { components } = processPlugins(
|
|
[
|
|
function ({ addComponents, theme }) {
|
|
addComponents({
|
|
'.foo': {
|
|
outline: theme('outline.black'),
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig({
|
|
theme: {
|
|
outline: {
|
|
black: ['2px dotted black', '4px'],
|
|
},
|
|
},
|
|
})
|
|
)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.foo {
|
|
outline: 2px dotted black;
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('box-shadow values are joined when retrieved using the theme function', () => {
|
|
const { components } = processPlugins(
|
|
[
|
|
function ({ addComponents, theme }) {
|
|
addComponents({
|
|
'.foo': {
|
|
boxShadow: theme('boxShadow.lol'),
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig({
|
|
theme: {
|
|
boxShadow: {
|
|
lol: ['width', 'height'],
|
|
},
|
|
},
|
|
})
|
|
)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.foo {
|
|
box-shadow: width, height;
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('transition-property values are joined when retrieved using the theme function', () => {
|
|
const { components } = processPlugins(
|
|
[
|
|
function ({ addComponents, theme }) {
|
|
addComponents({
|
|
'.foo': {
|
|
transitionProperty: theme('transitionProperty.lol'),
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig({
|
|
theme: {
|
|
transitionProperty: {
|
|
lol: ['width', 'height'],
|
|
},
|
|
},
|
|
})
|
|
)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.foo {
|
|
transition-property: width, height;
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('transition-duration values are joined when retrieved using the theme function', () => {
|
|
const { components } = processPlugins(
|
|
[
|
|
function ({ addComponents, theme }) {
|
|
addComponents({
|
|
'.foo': {
|
|
transitionDuration: theme('transitionDuration.lol'),
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig({
|
|
theme: {
|
|
transitionDuration: {
|
|
lol: ['width', 'height'],
|
|
},
|
|
},
|
|
})
|
|
)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.foo {
|
|
transition-duration: width, height;
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('transition-delay values are joined when retrieved using the theme function', () => {
|
|
const { components } = processPlugins(
|
|
[
|
|
function ({ addComponents, theme }) {
|
|
addComponents({
|
|
'.foo': {
|
|
transitionDelay: theme('transitionDelay.lol'),
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig({
|
|
theme: {
|
|
transitionDelay: {
|
|
lol: ['width', 'height'],
|
|
},
|
|
},
|
|
})
|
|
)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.foo {
|
|
transition-delay: width, height;
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('transition-timing-function values are joined when retrieved using the theme function', () => {
|
|
const { components } = processPlugins(
|
|
[
|
|
function ({ addComponents, theme }) {
|
|
addComponents({
|
|
'.foo': {
|
|
transitionTimingFunction: theme('transitionTimingFunction.lol'),
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig({
|
|
theme: {
|
|
transitionTimingFunction: {
|
|
lol: ['width', 'height'],
|
|
},
|
|
},
|
|
})
|
|
)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.foo {
|
|
transition-timing-function: width, height;
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('background-image values are joined when retrieved using the theme function', () => {
|
|
const { components } = processPlugins(
|
|
[
|
|
function ({ addComponents, theme }) {
|
|
addComponents({
|
|
'.foo': {
|
|
backgroundImage: theme('backgroundImage.lol'),
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig({
|
|
theme: {
|
|
backgroundImage: {
|
|
lol: ['width', 'height'],
|
|
},
|
|
},
|
|
})
|
|
)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.foo {
|
|
background-image: width, height;
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('background-size values are joined when retrieved using the theme function', () => {
|
|
const { components } = processPlugins(
|
|
[
|
|
function ({ addComponents, theme }) {
|
|
addComponents({
|
|
'.foo': {
|
|
backgroundSize: theme('backgroundSize.lol'),
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig({
|
|
theme: {
|
|
backgroundSize: {
|
|
lol: ['width', 'height'],
|
|
},
|
|
},
|
|
})
|
|
)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.foo {
|
|
background-size: width, height;
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('background-color values are joined when retrieved using the theme function', () => {
|
|
const { components } = processPlugins(
|
|
[
|
|
function ({ addComponents, theme }) {
|
|
addComponents({
|
|
'.foo': {
|
|
backgroundColor: theme('backgroundColor.lol'),
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig({
|
|
theme: {
|
|
backgroundColor: {
|
|
lol: ['width', 'height'],
|
|
},
|
|
},
|
|
})
|
|
)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.foo {
|
|
background-color: width, height;
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('cursor values are joined when retrieved using the theme function', () => {
|
|
const { components } = processPlugins(
|
|
[
|
|
function ({ addComponents, theme }) {
|
|
addComponents({
|
|
'.foo': {
|
|
cursor: theme('cursor.lol'),
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig({
|
|
theme: {
|
|
cursor: {
|
|
lol: ['width', 'height'],
|
|
},
|
|
},
|
|
})
|
|
)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.foo {
|
|
cursor: width, height;
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|
|
|
|
test('animation values are joined when retrieved using the theme function', () => {
|
|
const { components } = processPlugins(
|
|
[
|
|
function ({ addComponents, theme }) {
|
|
addComponents({
|
|
'.foo': {
|
|
animation: theme('animation.lol'),
|
|
},
|
|
})
|
|
},
|
|
],
|
|
makeConfig({
|
|
theme: {
|
|
animation: {
|
|
lol: ['width', 'height'],
|
|
},
|
|
},
|
|
})
|
|
)
|
|
|
|
expect(css(components)).toMatchCss(`
|
|
@layer components {
|
|
@variants {
|
|
.foo {
|
|
animation: width, height;
|
|
}
|
|
}
|
|
}
|
|
`)
|
|
})
|