mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
* change specific selector to universal selector This is the commit that we could "undo" in the future if we need it again. * simplify `relative-purge-paths` test This test doesn't require the "reset" selector (whether it is super specific or universal) Simplified it so that it tests the relative purge config and nothing else. * added css tagged template literal helpers This allows prettier to format the string as CSS. This improves formatting and will improve future diffs. * drop tailwind headers in the sanity tests Every time we bump the Tailwind version, the sanity tests fail, because the current version is encoded in the fixture files. This will ensure that all of the contents is still checked and the header is skipped. The header will be tested against a regex to ensure that it is still there. This should be a small but nice QoL improvement, so that we don't have to think about updating those tests whenever we fix bugs or land new features.
705 lines
16 KiB
JavaScript
705 lines
16 KiB
JavaScript
import postcss from 'postcss'
|
|
import path from 'path'
|
|
import tailwind from '../../src/jit/index.js'
|
|
|
|
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 = {
|
|
mode: 'jit',
|
|
purge: [
|
|
{
|
|
raw: '<div class="scale-x-110 rotate-3 skew-y-6"></div>',
|
|
},
|
|
],
|
|
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 = {
|
|
mode: 'jit',
|
|
purge: [
|
|
{
|
|
raw: '<div class="hover:scale-x-110 focus:rotate-3 hover:focus:skew-y-6"></div>',
|
|
},
|
|
],
|
|
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 = {
|
|
mode: 'jit',
|
|
purge: [
|
|
{
|
|
raw: '<div class="before:scale-x-110 after:rotate-3"></div>',
|
|
},
|
|
],
|
|
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 = {
|
|
mode: 'jit',
|
|
purge: [
|
|
{
|
|
raw: '<div class="group-hover:scale-x-110 peer-focus:rotate-3"></div>',
|
|
},
|
|
],
|
|
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 = {
|
|
mode: 'jit',
|
|
purge: [
|
|
{
|
|
raw: '<div class="group-hover:before:scale-x-110 peer-focus:after:rotate-3"></div>',
|
|
},
|
|
],
|
|
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 = {
|
|
mode: 'jit',
|
|
purge: [
|
|
{
|
|
raw: '<div class="group-hover:hover:before:scale-x-110 peer-focus:focus:after:rotate-3"></div>',
|
|
},
|
|
],
|
|
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 = {
|
|
mode: 'jit',
|
|
purge: [
|
|
{
|
|
raw: '<div class="foo"></div>',
|
|
},
|
|
],
|
|
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 = {
|
|
mode: 'jit',
|
|
purge: [
|
|
{
|
|
raw: '<div class="border border-red-500 md:border-2"></div>',
|
|
},
|
|
],
|
|
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 = {
|
|
mode: 'jit',
|
|
purge: [
|
|
{
|
|
raw: '<div class="shadow md:shadow-xl ring-1 ring-black/25"></div>',
|
|
},
|
|
],
|
|
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 = {
|
|
mode: 'jit',
|
|
purge: [
|
|
{
|
|
raw: '<div class=""></div>',
|
|
},
|
|
],
|
|
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 = {
|
|
mode: 'jit',
|
|
purge: [
|
|
{
|
|
raw: '<div class="rotate-3"></div>',
|
|
},
|
|
],
|
|
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 = {
|
|
mode: 'jit',
|
|
purge: [
|
|
{
|
|
raw: '<div class="foo"></div>',
|
|
},
|
|
],
|
|
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);
|
|
}
|
|
`)
|
|
})
|
|
})
|