import postcss from 'postcss' import path from 'path' import tailwind from '../../src' function run(input, config = {}) { const { currentTestName } = expect.getState() return postcss(tailwind(config)).process(input, { from: `${path.resolve(__filename)}?test=${currentTestName}`, }) } function css(templates) { return templates.join('') } test('basic utilities', async () => { let config = { content: [ { raw: '
', }, ], theme: {}, plugins: [], corePlugins: ['transform', 'scale', 'rotate', 'skew'], } let input = css` @tailwind base; /* --- */ @tailwind utilities; ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` *, ::before, ::after { --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-transform: translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } /* --- */ .rotate-3 { --tw-rotate: 3deg; transform: var(--tw-transform); } .skew-y-6 { --tw-skew-y: 6deg; transform: var(--tw-transform); } .scale-x-110 { --tw-scale-x: 1.1; transform: var(--tw-transform); } `) }) }) test('with pseudo-class variants', async () => { let config = { content: [ { raw: '', }, ], theme: {}, plugins: [], corePlugins: ['transform', 'scale', 'rotate', 'skew'], } let input = css` @tailwind base; /* --- */ @tailwind utilities; ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` *, ::before, ::after { --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-transform: translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } /* --- */ .hover\\:scale-x-110:hover { --tw-scale-x: 1.1; transform: var(--tw-transform); } .focus\\:rotate-3:focus { --tw-rotate: 3deg; transform: var(--tw-transform); } .hover\\:focus\\:skew-y-6:hover:focus { --tw-skew-y: 6deg; transform: var(--tw-transform); } `) }) }) test('with pseudo-element variants', async () => { let config = { content: [ { raw: '', }, ], theme: {}, plugins: [], corePlugins: ['transform', 'scale', 'rotate', 'skew'], } let input = css` @tailwind base; /* --- */ @tailwind utilities; ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` *, ::before, ::after { --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-transform: translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } /* --- */ .before\\:scale-x-110::before { content: ''; --tw-scale-x: 1.1; transform: var(--tw-transform); } .after\\:rotate-3::after { content: ''; --tw-rotate: 3deg; transform: var(--tw-transform); } `) }) }) test('with multi-class variants', async () => { let config = { content: [ { raw: '', }, ], theme: {}, plugins: [], corePlugins: ['transform', 'scale', 'rotate', 'skew'], } let input = css` @tailwind base; /* --- */ @tailwind utilities; ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` *, ::before, ::after { --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-transform: translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } /* --- */ .group:hover .group-hover\\:scale-x-110 { --tw-scale-x: 1.1; transform: var(--tw-transform); } .peer:focus ~ .peer-focus\\:rotate-3 { --tw-rotate: 3deg; transform: var(--tw-transform); } `) }) }) test('with multi-class pseudo-element variants', async () => { let config = { content: [ { raw: '', }, ], theme: {}, plugins: [], corePlugins: ['transform', 'scale', 'rotate', 'skew'], } let input = css` @tailwind base; /* --- */ @tailwind utilities; ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` *, ::before, ::after { --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-transform: translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } /* --- */ .group:hover .group-hover\\:before\\:scale-x-110::before { content: ''; --tw-scale-x: 1.1; transform: var(--tw-transform); } .peer:focus ~ .peer-focus\\:after\\:rotate-3::after { content: ''; --tw-rotate: 3deg; transform: var(--tw-transform); } `) }) }) test('with multi-class pseudo-element and pseudo-class variants', async () => { let config = { content: [ { raw: '', }, ], theme: {}, plugins: [], corePlugins: ['transform', 'scale', 'rotate', 'skew'], } let input = css` @tailwind base; /* --- */ @tailwind utilities; ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` *, ::before, ::after { --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-transform: translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } /* --- */ .group:hover .group-hover\\:hover\\:before\\:scale-x-110:hover::before { content: ''; --tw-scale-x: 1.1; transform: var(--tw-transform); } .peer:focus ~ .peer-focus\\:focus\\:after\\:rotate-3:focus::after { content: ''; --tw-rotate: 3deg; transform: var(--tw-transform); } `) }) }) test('with apply', async () => { let config = { content: [ { raw: '', }, ], theme: {}, plugins: [], corePlugins: ['transform', 'scale', 'rotate', 'skew'], } let input = css` @tailwind base; /* --- */ @tailwind utilities; @layer utilities { .foo { @apply rotate-3; } } .bar { @apply before:scale-110; } .baz::before { content: ''; @apply rotate-45; } .whats ~ .next > span:hover { @apply skew-x-6; } .media-queries { @apply md:rotate-45; } .a, .b, .c { @apply skew-y-3; } .a, .b { @apply rotate-45; } .a::before, .b::after { @apply rotate-90; } .recursive { @apply foo; } ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` *, ::before, ::after { --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-transform: translateX(var(--tw-translate-x)) translateY(var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } /* --- */ .foo { --tw-rotate: 3deg; transform: var(--tw-transform); } .bar::before { content: ''; --tw-scale-x: 1.1; --tw-scale-y: 1.1; transform: var(--tw-transform); } .baz::before { content: ''; --tw-rotate: 45deg; transform: var(--tw-transform); } .whats ~ .next > span:hover { --tw-skew-x: 6deg; transform: var(--tw-transform); } @media (min-width: 768px) { .media-queries { --tw-rotate: 45deg; transform: var(--tw-transform); } } .a, .b, .c { --tw-skew-y: 3deg; transform: var(--tw-transform); } .a, .b { --tw-rotate: 45deg; transform: var(--tw-transform); } .a::before, .b::after { --tw-rotate: 90deg; transform: var(--tw-transform); } .recursive { --tw-rotate: 3deg; transform: var(--tw-transform); } `) }) }) test('with borders', async () => { let config = { content: [ { raw: '', }, ], theme: {}, plugins: [], corePlugins: ['borderWidth', 'borderColor', 'borderOpacity'], } let input = css` @tailwind base; /* --- */ @tailwind utilities; ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` *, ::before, ::after { --tw-border-opacity: 1; border-color: rgba(229, 231, 235, var(--tw-border-opacity)); } /* --- */ .border { border-width: 1px; } .border-red-500 { --tw-border-opacity: 1; border-color: rgba(239, 68, 68, var(--tw-border-opacity)); } @media (min-width: 768px) { .md\\:border-2 { border-width: 2px; } } `) }) }) test('with shadows', async () => { let config = { content: [ { raw: '', }, ], theme: {}, plugins: [], corePlugins: ['boxShadow', 'ringColor', 'ringWidth'], } let input = css` @tailwind base; /* --- */ @tailwind utilities; ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` *, ::before, ::after { --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-ring-inset: var(--tw-empty, /*!*/ /*!*/); --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgba(59, 130, 246, 0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; } /* --- */ .shadow { --tw-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } .ring-1 { --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); } .ring-black\\/25 { --tw-ring-color: rgba(0, 0, 0, 0.25); } @media (min-width: 768px) { .md\\:shadow-xl { --tw-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } } `) }) }) test('when no utilities that need the defaults are used', async () => { let config = { content: [ { raw: '', }, ], theme: {}, plugins: [], corePlugins: ['transform', 'scale', 'rotate', 'skew'], } let input = css` @tailwind base; /* --- */ @tailwind utilities; ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` /* --- */ `) }) }) test('when a utility uses defaults but they do not exist', async () => { let config = { content: [ { raw: '', }, ], theme: {}, plugins: [], corePlugins: ['rotate'], } let input = css` @tailwind base; /* --- */ @tailwind utilities; ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` /* --- */ .rotate-3 { --tw-rotate: 3deg; transform: var(--tw-transform); } `) }) }) test('selectors are reduced to the lowest possible specificity', async () => { let config = { content: [ { raw: '', }, ], theme: {}, plugins: [], corePlugins: [], } let input = css` @defaults test { --color: black; } /* --- */ .foo { @defaults test; background-color: var(--color); } #app { @defaults test; border-color: var(--color); } span#page { @defaults test; color: var(--color); } div[data-foo='bar']#other { @defaults test; fill: var(--color); } div[data-bar='baz'] { @defaults test; stroke: var(--color); } article { @defaults test; --article: var(--color); } div[data-foo='bar']#another::before { @defaults test; fill: var(--color); } ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` *, ::before, ::after { --color: black; } /* --- */ .foo { background-color: var(--color); } #app { border-color: var(--color); } span#page { color: var(--color); } div[data-foo='bar']#other { fill: var(--color); } div[data-bar='baz'] { stroke: var(--color); } article { --article: var(--color); } div[data-foo='bar']#another::before { fill: var(--color); } `) }) })