1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354 |
- import { run, html, css, defaults } from './util/run'
- import { flagEnabled } from '../src/featureFlags'
- test('basic arbitrary variants', () => {
- let config = {
- content: [{ raw: html`<div class="[&>*]:underline"></div>` }],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- .\[\&\>\*\]\:underline > * {
- text-decoration-line: underline;
- }
- `)
- })
- })
- test('spaces in selector (using _)', () => {
- let config = {
- content: [
- {
- raw: html`<div class="[.a.b_&]:underline"></div>`,
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- .a.b .\[\.a\.b_\&\]\:underline {
- text-decoration-line: underline;
- }
- `)
- })
- })
- test('arbitrary variants with modifiers', () => {
- let config = {
- content: [{ raw: html`<div class="dark:lg:hover:[&>*]:underline"></div>` }],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- @media (prefers-color-scheme: dark) {
- @media (min-width: 1024px) {
- .dark\:lg\:hover\:\[\&\>\*\]\:underline > :hover {
- text-decoration-line: underline;
- }
- }
- }
- `)
- })
- })
- test('variants without & or an at-rule are ignored', () => {
- let config = {
- content: [
- {
- raw: html`
- <div class="[div]:underline"></div>
- <div class="[:hover]:underline"></div>
- <div class="[wtf-bbq]:underline"></div>
- <div class="[lol]:hover:underline"></div>
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- `)
- })
- })
- test('arbitrary variants are sorted after other variants', () => {
- let config = {
- content: [{ raw: html`<div class="underline lg:underline [&>*]:underline"></div>` }],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- .underline {
- text-decoration-line: underline;
- }
- @media (min-width: 1024px) {
- .lg\:underline {
- text-decoration-line: underline;
- }
- }
- .\[\&\>\*\]\:underline > * {
- text-decoration-line: underline;
- }
- `)
- })
- })
- test('using the important modifier', () => {
- let config = {
- content: [{ raw: html`<div class="[&>*]:!underline"></div>` }],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- .\[\&\>\*\]\:\!underline > * {
- text-decoration-line: underline !important;
- }
- `)
- })
- })
- test('at-rules', () => {
- let config = {
- content: [{ raw: html`<div class="[@supports(what:ever)]:underline"></div>` }],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- @supports (what: ever) {
- .\[\@supports\(what\:ever\)\]\:underline {
- text-decoration-line: underline;
- }
- }
- `)
- })
- })
- test('nested at-rules', () => {
- let config = {
- content: [
- {
- raw: html`<div class="[@media_screen{@media(hover:hover)}]:underline"></div>`,
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- @media screen {
- @media (hover: hover) {
- .\[\@media_screen\{\@media\(hover\:hover\)\}\]\:underline {
- text-decoration-line: underline;
- }
- }
- }
- `)
- })
- })
- test('at-rules with selector modifications', () => {
- let config = {
- content: [{ raw: html`<div class="[@media(hover:hover){&:hover}]:underline"></div>` }],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- @media (hover: hover) {
- .\[\@media\(hover\:hover\)\{\&\:hover\}\]\:underline:hover {
- text-decoration-line: underline;
- }
- }
- `)
- })
- })
- test('nested at-rules with selector modifications', () => {
- let config = {
- content: [
- {
- raw: html`<div class="[@media_screen{@media(hover:hover){&:hover}}]:underline"></div>`,
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- @media screen {
- @media (hover: hover) {
- .\[\@media_screen\{\@media\(hover\:hover\)\{\&\:hover\}\}\]\:underline:hover {
- text-decoration-line: underline;
- }
- }
- }
- `)
- })
- })
- test('attribute selectors', () => {
- let config = {
- content: [{ raw: html`<div class="[&[data-open]]:underline"></div>` }],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- .\[\&\[data-open\]\]\:underline[data-open] {
- text-decoration-line: underline;
- }
- `)
- })
- })
- test('multiple attribute selectors', () => {
- let config = {
- content: [{ raw: html`<div class="[&[data-foo][data-bar]:not([data-baz])]:underline"></div>` }],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- .\[\&\[data-foo\]\[data-bar\]\:not\(\[data-baz\]\)\]\:underline[data-foo][data-bar]:not([data-baz]) {
- text-decoration-line: underline;
- }
- `)
- })
- })
- test('multiple attribute selectors with custom separator (1)', () => {
- let config = {
- separator: '__',
- content: [
- { raw: html`<div class="[&[data-foo][data-bar]:not([data-baz])]__underline"></div>` },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- .\[\&\[data-foo\]\[data-bar\]\:not\(\[data-baz\]\)\]__underline[data-foo][data-bar]:not([data-baz]) {
- text-decoration-line: underline;
- }
- `)
- })
- })
- test('multiple attribute selectors with custom separator (2)', () => {
- let config = {
- separator: '_@',
- content: [
- { raw: html`<div class="[&[data-foo][data-bar]:not([data-baz])]_@underline"></div>` },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- .\[\&\[data-foo\]\[data-bar\]\:not\(\[data-baz\]\)\]_\@underline[data-foo][data-bar]:not([data-baz]) {
- text-decoration-line: underline;
- }
- `)
- })
- })
- test('with @apply', () => {
- let config = {
- content: [
- {
- raw: html`<div class="foo"></div>`,
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = `
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- .foo {
- @apply [@media_screen{@media(hover:hover){&:hover}}]:underline;
- }
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- @media screen {
- @media (hover: hover) {
- .foo:hover {
- text-decoration-line: underline;
- }
- }
- }
- `)
- })
- })
- test('keeps escaped underscores', () => {
- let config = {
- content: [
- {
- raw: '<div class="[&_.foo\\_\\_bar]:underline"></div>',
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- .\[\&_\.foo\\_\\_bar\]\:underline .foo__bar {
- text-decoration-line: underline;
- }
- `)
- })
- })
- test('keeps escaped underscores with multiple arbitrary variants', () => {
- let config = {
- content: [
- {
- raw: '<div class="[&_.foo\\_\\_bar]:[&_.bar\\_\\_baz]:underline"></div>',
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- .\[\&_\.foo\\_\\_bar\]\:\[\&_\.bar\\_\\_baz\]\:underline .bar__baz .foo__bar {
- text-decoration-line: underline;
- }
- `)
- })
- })
- test('keeps escaped underscores in arbitrary variants mixed with normal variants', () => {
- let config = {
- content: [
- {
- raw: `
- <div class="[&_.foo\\_\\_bar]:hover:underline"></div>
- <div class="hover:[&_.foo\\_\\_bar]:underline"></div>
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- .\[\&_\.foo\\_\\_bar\]\:hover\:underline:hover .foo__bar,
- .hover\:\[\&_\.foo\\_\\_bar\]\:underline .foo__bar:hover {
- text-decoration-line: underline;
- }
- `)
- })
- })
- test('allows attribute variants with quotes', () => {
- let config = {
- content: [
- {
- raw: `
- <div class="[&[data-test='2']]:underline"></div>
- <div class='[&[data-test="2"]]:underline'></div>
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- .\[\&\[data-test\=\"2\"\]\]\:underline[data-test='2'],
- .\[\&\[data-test\=\'2\'\]\]\:underline[data-test='2'] {
- text-decoration-line: underline;
- }
- `)
- })
- })
- test('classes in arbitrary variants should not be prefixed', () => {
- let config = {
- prefix: 'tw-',
- content: [
- {
- raw: `
- <div class="[.foo_&]:tw-text-red-400">should not be red</div>
- <div class="foo">
- <div class="[.foo_&]:tw-text-red-400">should be red</div>
- </div>
- <div class="[&_.foo]:tw-text-red-400">
- <div>should not be red</div>
- <div class="foo">should be red</div>
- </div>
- <div class="hover:[&_.foo]:tw-text-red-400">
- <div>should not be red</div>
- <div class="foo">should be red</div>
- </div>
- <div class="[&_.foo]:hover:tw-text-red-400">
- <div>should not be red</div>
- <div class="foo">should be red</div>
- </div>
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .\[\&_\.foo\]\:tw-text-red-400 .foo,
- .\[\&_\.foo\]\:hover\:tw-text-red-400:hover .foo,
- .hover\:\[\&_\.foo\]\:tw-text-red-400 .foo:hover,
- .foo .\[\.foo_\&\]\:tw-text-red-400 {
- --tw-text-opacity: 1;
- color: rgb(248 113 113 / var(--tw-text-opacity));
- }
- `)
- })
- })
- test('classes in the same arbitrary variant should not be prefixed', () => {
- let config = {
- prefix: 'tw-',
- content: [
- {
- raw: `
- <div class="[.foo_&]:tw-font-bold">should not be red</div>
- <div class="foo">
- <div class="[.foo_&]:tw-font-bold">should be red</div>
- </div>
- <div class="[&_.foo]:tw-font-bold [&_.foo]:tw-font-bold">
- <div>should not be red</div>
- <div class="foo">should be red</div>
- </div>
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .\[\&_\.foo\]\:tw-font-bold .foo,
- .foo .\[\.foo_\&\]\:tw-font-bold {
- font-weight: 700;
- }
- `)
- })
- })
- it('should support aria variants', () => {
- let config = {
- content: [
- {
- raw: html`
- <div>
- <div class="aria-checked:underline"></div>
- <div class="aria-[sort=ascending]:underline"></div>
- <div class="aria-[labelledby='a_b']:underline"></div>
- <div class="group-aria-checked:underline"></div>
- <div class="peer-aria-checked:underline"></div>
- <div class="group-aria-checked/foo:underline"></div>
- <div class="peer-aria-checked/foo:underline"></div>
- <div class="group-aria-[sort=ascending]:underline"></div>
- <div class="peer-aria-[sort=ascending]:underline"></div>
- <div class="group-aria-[labelledby='a_b']:underline"></div>
- <div class="peer-aria-[labelledby='a_b']:underline"></div>
- <div class="group-aria-[sort=ascending]/foo:underline"></div>
- <div class="peer-aria-[sort=ascending]/foo:underline"></div>
- </div>
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(
- flagEnabled(config, 'oxideParser')
- ? css`
- .aria-checked\:underline[aria-checked='true'],
- .aria-\[labelledby\=\'a_b\'\]\:underline[aria-labelledby='a b'],
- .aria-\[sort\=ascending\]\:underline[aria-sort='ascending'],
- .group\/foo[aria-checked='true'] .group-aria-checked\/foo\:underline,
- .group[aria-checked='true'] .group-aria-checked\:underline,
- .group[aria-labelledby='a b'] .group-aria-\[labelledby\=\'a_b\'\]\:underline,
- .group\/foo[aria-sort='ascending'] .group-aria-\[sort\=ascending\]\/foo\:underline,
- .group[aria-sort='ascending'] .group-aria-\[sort\=ascending\]\:underline,
- .peer\/foo[aria-checked='true'] ~ .peer-aria-checked\/foo\:underline,
- .peer[aria-checked='true'] ~ .peer-aria-checked\:underline,
- .peer[aria-labelledby='a b'] ~ .peer-aria-\[labelledby\=\'a_b\'\]\:underline,
- .peer\/foo[aria-sort='ascending'] ~ .peer-aria-\[sort\=ascending\]\/foo\:underline,
- .peer[aria-sort='ascending'] ~ .peer-aria-\[sort\=ascending\]\:underline {
- text-decoration-line: underline;
- }
- `
- : css`
- .underline,
- .aria-checked\:underline[aria-checked='true'],
- .aria-\[labelledby\=\'a_b\'\]\:underline[aria-labelledby='a b'],
- .aria-\[sort\=ascending\]\:underline[aria-sort='ascending'],
- .group\/foo[aria-checked='true'] .group-aria-checked\/foo\:underline,
- .group[aria-checked='true'] .group-aria-checked\:underline,
- .group[aria-labelledby='a b'] .group-aria-\[labelledby\=\'a_b\'\]\:underline,
- .group\/foo[aria-sort='ascending'] .group-aria-\[sort\=ascending\]\/foo\:underline,
- .group[aria-sort='ascending'] .group-aria-\[sort\=ascending\]\:underline,
- .peer\/foo[aria-checked='true'] ~ .peer-aria-checked\/foo\:underline,
- .peer[aria-checked='true'] ~ .peer-aria-checked\:underline,
- .peer[aria-labelledby='a b'] ~ .peer-aria-\[labelledby\=\'a_b\'\]\:underline,
- .peer\/foo[aria-sort='ascending'] ~ .peer-aria-\[sort\=ascending\]\/foo\:underline,
- .peer[aria-sort='ascending'] ~ .peer-aria-\[sort\=ascending\]\:underline {
- text-decoration-line: underline;
- }
- `
- )
- })
- })
- it('should support data variants', () => {
- let config = {
- theme: {
- data: {
- checked: 'ui~="checked"',
- },
- },
- content: [
- {
- raw: html`
- <div>
- <div class="data-checked:underline"></div>
- <div class="data-[position=top]:underline"></div>
- <div class="data-[foo='bar_baz']:underline"></div>
- <div class="group-data-checked:underline"></div>
- <div class="peer-data-checked:underline"></div>
- <div class="group-data-checked/foo:underline"></div>
- <div class="peer-data-checked/foo:underline"></div>
- <div class="group-data-[position=top]:underline"></div>
- <div class="peer-data-[position=top]:underline"></div>
- <div class="group-data-[foo='bar_baz']:underline"></div>
- <div class="peer-data-[foo='bar_baz']:underline"></div>
- <div class="group-data-[position=top]/foo:underline"></div>
- <div class="peer-data-[position=top]/foo:underline"></div>
- </div>
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(
- flagEnabled(config, 'oxideParser')
- ? css`
- .data-checked\:underline[data-ui~='checked'],
- .data-\[foo\=\'bar_baz\'\]\:underline[data-foo='bar baz'],
- .data-\[position\=top\]\:underline[data-position='top'],
- .group\/foo[data-ui~='checked'] .group-data-checked\/foo\:underline,
- .group[data-ui~='checked'] .group-data-checked\:underline,
- .group[data-foo='bar baz'] .group-data-\[foo\=\'bar_baz\'\]\:underline,
- .group\/foo[data-position='top'] .group-data-\[position\=top\]\/foo\:underline,
- .group[data-position='top'] .group-data-\[position\=top\]\:underline,
- .peer\/foo[data-ui~='checked'] ~ .peer-data-checked\/foo\:underline,
- .peer[data-ui~='checked'] ~ .peer-data-checked\:underline,
- .peer[data-foo='bar baz'] ~ .peer-data-\[foo\=\'bar_baz\'\]\:underline,
- .peer\/foo[data-position='top'] ~ .peer-data-\[position\=top\]\/foo\:underline,
- .peer[data-position='top'] ~ .peer-data-\[position\=top\]\:underline {
- text-decoration-line: underline;
- }
- `
- : css`
- .underline,
- .data-checked\:underline[data-ui~='checked'],
- .data-\[foo\=\'bar_baz\'\]\:underline[data-foo='bar baz'],
- .data-\[position\=top\]\:underline[data-position='top'],
- .group\/foo[data-ui~='checked'] .group-data-checked\/foo\:underline,
- .group[data-ui~='checked'] .group-data-checked\:underline,
- .group[data-foo='bar baz'] .group-data-\[foo\=\'bar_baz\'\]\:underline,
- .group\/foo[data-position='top'] .group-data-\[position\=top\]\/foo\:underline,
- .group[data-position='top'] .group-data-\[position\=top\]\:underline,
- .peer\/foo[data-ui~='checked'] ~ .peer-data-checked\/foo\:underline,
- .peer[data-ui~='checked'] ~ .peer-data-checked\:underline,
- .peer[data-foo='bar baz'] ~ .peer-data-\[foo\=\'bar_baz\'\]\:underline,
- .peer\/foo[data-position='top'] ~ .peer-data-\[position\=top\]\/foo\:underline,
- .peer[data-position='top'] ~ .peer-data-\[position\=top\]\:underline {
- text-decoration-line: underline;
- }
- `
- )
- })
- })
- it('should support supports', () => {
- let config = {
- theme: {
- supports: {
- grid: 'display: grid',
- },
- },
- content: [
- {
- raw: html`
- <div>
- <!-- Property check -->
- <div class="supports-[display:grid]:grid"></div>
- <!-- Value with spaces, needs to be normalized -->
- <div class="supports-[transform-origin:5%_5%]:underline"></div>
- <!-- Selectors (raw) -->
- <div class="supports-[selector(A_>_B)]:underline"></div>
- <!-- 'not' check (raw) -->
- <div class="supports-[not(foo:bar)]:underline"></div>
- <!-- 'or' check (raw) -->
- <div class="supports-[(foo:bar)or(bar:baz)]:underline"></div>
- <!-- 'and' check (raw) -->
- <div class="supports-[(foo:bar)and(bar:baz)]:underline"></div>
- <!-- No value give for the property, defaulting to prop: var(--tw) -->
- <div class="supports-[container-type]:underline"></div>
- <!-- Named supports usage -->
- <div class="supports-grid:underline"></div>
- </div>
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @supports (display: grid) {
- .supports-grid\:underline {
- text-decoration-line: underline;
- }
- .supports-\[display\:grid\]\:grid {
- display: grid;
- }
- }
- @supports (foo: bar) and (bar: baz) {
- .supports-\[\(foo\:bar\)and\(bar\:baz\)\]\:underline {
- text-decoration-line: underline;
- }
- }
- @supports (foo: bar) or (bar: baz) {
- .supports-\[\(foo\:bar\)or\(bar\:baz\)\]\:underline {
- text-decoration-line: underline;
- }
- }
- @supports (container-type: var(--tw)) {
- .supports-\[container-type\]\:underline {
- text-decoration-line: underline;
- }
- }
- @supports not (foo: bar) {
- .supports-\[not\(foo\:bar\)\]\:underline {
- text-decoration-line: underline;
- }
- }
- @supports selector(A > B) {
- .supports-\[selector\(A_\>_B\)\]\:underline {
- text-decoration-line: underline;
- }
- }
- @supports (transform-origin: 5% 5%) {
- .supports-\[transform-origin\:5\%_5\%\]\:underline {
- text-decoration-line: underline;
- }
- }
- `)
- })
- })
- test('has-* variants with arbitrary values', () => {
- let config = {
- theme: {},
- content: [
- {
- raw: html`
- <div>
- <figure class="has-[figcaption]:inline-block"></figure>
- <div class="has-[.foo]:flex"></div>
- <div class="has-[.foo:hover]:block"></div>
- <div class="has-[[data-active]]:inline"></div>
- <div class="has-[>_.potato]:table"></div>
- <div class="has-[+_h2]:grid"></div>
- <div class="has-[>_h1_+_h2]:contents"></div>
- <div class="has-[h2]:has-[.banana]:hidden"></div>
- </div>
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .has-\[\.foo\:hover\]\:block:has(.foo:hover) {
- display: block;
- }
- .has-\[figcaption\]\:inline-block:has(figcaption) {
- display: inline-block;
- }
- .has-\[\[data-active\]\]\:inline:has([data-active]) {
- display: inline;
- }
- .has-\[\.foo\]\:flex:has(.foo) {
- display: flex;
- }
- .has-\[\>_\.potato\]\:table:has(> .potato) {
- display: table;
- }
- .has-\[\+_h2\]\:grid:has(+ h2) {
- display: grid;
- }
- .has-\[\>_h1_\+_h2\]\:contents:has(> h1 + h2) {
- display: contents;
- }
- .has-\[h2\]\:has-\[\.banana\]\:hidden:has(.banana):has(h2) {
- display: none;
- }
- `)
- })
- })
- test('group-has-* variants with arbitrary values', () => {
- let config = {
- theme: {},
- content: [
- {
- raw: html`
- <div class="group">
- <div class="group-has-[>_h1_+_.foo]:block"></div>
- </div>
- <div class="group/two">
- <div class="group-has-[>_h1_+_.foo]/two:flex"></div>
- </div>
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .group:has(> h1 + .foo) .group-has-\[\>_h1_\+_\.foo\]\:block {
- display: block;
- }
- .group\/two:has(> h1 + .foo) .group-has-\[\>_h1_\+_\.foo\]\/two\:flex {
- display: flex;
- }
- `)
- })
- })
- test('peer-has-* variants with arbitrary values', () => {
- let config = {
- theme: {},
- content: [
- {
- raw: html`
- <div>
- <div className="peer"></div>
- <div class="peer-has-[>_h1_+_.foo]:block"></div>
- </div>
- <div>
- <div className="peer"></div>
- <div class="peer-has-[>_h1_+_.foo]/two:flex"></div>
- </div>
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .peer:has(> h1 + .foo) ~ .peer-has-\[\>_h1_\+_\.foo\]\:block {
- display: block;
- }
- .peer\/two:has(> h1 + .foo) ~ .peer-has-\[\>_h1_\+_\.foo\]\/two\:flex {
- display: flex;
- }
- `)
- })
- })
- it('should be possible to use modifiers and arbitrary groups', () => {
- let config = {
- content: [
- {
- raw: html`
- <div>
- <div class="group">
- <!-- Default group usage -->
- <div class="group-hover:underline"></div>
- <!-- Arbitrary variants with pseudo class for group -->
- <!-- With & -->
- <div class="group-[&:focus]:underline"></div>
- <!-- Without & -->
- <div class="group-[:hover]:underline"></div>
- <!-- Arbitrary variants with attributes selectors for group -->
- <!-- With & -->
- <div class="group-[&[data-open]]:underline"></div>
- <!-- Without & -->
- <div class="group-[[data-open]]:underline"></div>
- <!-- Arbitrary variants with other selectors -->
- <!-- With & -->
- <div class="group-[.in-foo_&]:underline"></div>
- <!-- Without & -->
- <div class="group-[.in-foo]:underline"></div>
- </div>
- <!-- The same as above, but with modifiers -->
- <div class="group/foo">
- <div class="group-hover/foo:underline"></div>
- <div class="group-[&:focus]/foo:underline"></div>
- <div class="group-[:hover]/foo:underline"></div>
- <div class="group-[&[data-open]]/foo:underline"></div>
- <div class="group-[[data-open]]/foo:underline"></div>
- <div class="group-[.in-foo_&]/foo:underline"></div>
- <div class="group-[.in-foo]/foo:underline"></div>
- </div>
- </div>
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .group\/foo:hover .group-hover\/foo\:underline,
- .group:hover .group-hover\:underline,
- .group\/foo:focus .group-\[\&\:focus\]\/foo\:underline,
- .group:focus .group-\[\&\:focus\]\:underline,
- .group\/foo[data-open] .group-\[\&\[data-open\]\]\/foo\:underline,
- .group[data-open] .group-\[\&\[data-open\]\]\:underline,
- .group\/foo.in-foo .group-\[\.in-foo\]\/foo\:underline,
- .group.in-foo .group-\[\.in-foo\]\:underline,
- .in-foo .group\/foo .group-\[\.in-foo_\&\]\/foo\:underline,
- .in-foo .group .group-\[\.in-foo_\&\]\:underline,
- .group\/foo:hover .group-\[\:hover\]\/foo\:underline,
- .group:hover .group-\[\:hover\]\:underline,
- .group\/foo[data-open] .group-\[\[data-open\]\]\/foo\:underline,
- .group[data-open] .group-\[\[data-open\]\]\:underline {
- text-decoration-line: underline;
- }
- `)
- })
- })
- it('should be possible to use modifiers and arbitrary peers', () => {
- let config = {
- content: [
- {
- raw: html`
- <div>
- <div class="peer"></div>
- <!-- Default peer usage -->
- <div class="peer-hover:underline"></div>
- <!-- Arbitrary variants with pseudo class for peer -->
- <!-- With & -->
- <div class="peer-[&:focus]:underline"></div>
- <!-- Without & -->
- <div class="peer-[:hover]:underline"></div>
- <!-- Arbitrary variants with attributes selectors for peer -->
- <!-- With & -->
- <div class="peer-[&[data-open]]:underline"></div>
- <!-- Without & -->
- <div class="peer-[[data-open]]:underline"></div>
- <!-- Arbitrary variants with other selectors -->
- <!-- With & -->
- <div class="peer-[.in-foo_&]:underline"></div>
- <!-- Without & -->
- <div class="peer-[.in-foo]:underline"></div>
- <!-- The same as above, but with modifiers -->
- <div class="peer/foo"></div>
- <div class="peer-hover/foo:underline"></div>
- <div class="peer-[&:focus]/foo:underline"></div>
- <div class="peer-[:hover]/foo:underline"></div>
- <div class="peer-[&[data-open]]/foo:underline"></div>
- <div class="peer-[[data-open]]/foo:underline"></div>
- <div class="peer-[.in-foo_&]/foo:underline"></div>
- <div class="peer-[.in-foo]/foo:underline"></div>
- </div>
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .peer\/foo:hover ~ .peer-hover\/foo\:underline,
- .peer:hover ~ .peer-hover\:underline,
- .peer\/foo:focus ~ .peer-\[\&\:focus\]\/foo\:underline,
- .peer:focus ~ .peer-\[\&\:focus\]\:underline,
- .peer\/foo[data-open] ~ .peer-\[\&\[data-open\]\]\/foo\:underline,
- .peer[data-open] ~ .peer-\[\&\[data-open\]\]\:underline,
- .peer\/foo.in-foo ~ .peer-\[\.in-foo\]\/foo\:underline,
- .peer.in-foo ~ .peer-\[\.in-foo\]\:underline,
- .in-foo .peer\/foo ~ .peer-\[\.in-foo_\&\]\/foo\:underline,
- .in-foo .peer ~ .peer-\[\.in-foo_\&\]\:underline,
- .peer\/foo:hover ~ .peer-\[\:hover\]\/foo\:underline,
- .peer:hover ~ .peer-\[\:hover\]\:underline,
- .peer\/foo[data-open] ~ .peer-\[\[data-open\]\]\/foo\:underline,
- .peer[data-open] ~ .peer-\[\[data-open\]\]\:underline {
- text-decoration-line: underline;
- }
- `)
- })
- })
- it('Arbitrary variants are ordered alphabetically', () => {
- let config = {
- content: [
- {
- raw: html`
- <div>
- <div class="[&::b]:underline"></div>
- <div class="[&::a]:underline"></div>
- <div class="[&::c]:underline"></div>
- <div class="[&::b]:underline"></div>
- </div>
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .\[\&\:\:a\]\:underline::a {
- text-decoration-line: underline;
- }
- .\[\&\:\:b\]\:underline::b {
- text-decoration-line: underline;
- }
- .\[\&\:\:c\]\:underline::c {
- text-decoration-line: underline;
- }
- `)
- })
- })
- it('Arbitrary variants support multiple attribute selectors', () => {
- let config = {
- content: [
- {
- raw: html` <div class="[[data-foo='bar'][data-baz]_&]:underline"></div> `,
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- [data-foo='bar'][data-baz] .\[\[data-foo\=\'bar\'\]\[data-baz\]_\&\]\:underline {
- text-decoration-line: underline;
- }
- `)
- })
- })
- it('Invalid arbitrary variants selectors should produce nothing instead of failing', () => {
- let config = {
- content: [
- {
- raw: html`
- <div class="[&;foo]:underline"></div>
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css``)
- })
- })
- it('should output responsive variants + stacked variants in the right order', () => {
- let config = {
- content: [
- {
- raw: html`
- <div class="xl:p-1"></div>
- <div class="md:[&_ul]:flex-row"></div>
- <div class="[&_ul]:flex"></div>
- <div class="[&_ul]:flex-col"></div>
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 1280px) {
- .xl\:p-1 {
- padding: 0.25rem;
- }
- }
- .\[\&_ul\]\:flex ul {
- display: flex;
- }
- .\[\&_ul\]\:flex-col ul {
- flex-direction: column;
- }
- @media (min-width: 768px) {
- .md\:\[\&_ul\]\:flex-row ul {
- flex-direction: row;
- }
- }
- `)
- })
- })
- it('it should discard arbitrary variants with multiple selectors', () => {
- let config = {
- content: [
- {
- raw: html`
- <div class="p-1"></div>
- <div class="[div]:p-1"></div>
- <div class="[div_&]:p-1"></div>
- <div class="[div,span]:p-1"></div>
- <div class="[div_&,span]:p-1"></div>
- <div class="[div,span_&]:p-1"></div>
- <div class="[div_&,span_&]:p-1"></div>
- <div class="hover:[div]:p-1"></div>
- <div class="hover:[div_&]:p-1"></div>
- <div class="hover:[div,span]:p-1"></div>
- <div class="hover:[div_&,span]:p-1"></div>
- <div class="hover:[div,span_&]:p-1"></div>
- <div class="hover:[div_&,span_&]:p-1"></div>
- <div class="hover:[:is(span,div)_&]:p-1"></div>
- `,
- },
- {
- // escaped commas are a-ok
- // This is separate because prettier complains about `\,` in the template string
- raw: '<div class="hover:[.span\\,div_&]:p-1"></div>',
- },
- ],
- corePlugins: { preflight: false },
- }
- let input = css`
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .p-1,
- .span\,div .hover\:\[\.span\\\,div_\&\]\:p-1:hover,
- :is(span, div) .hover\:\[\:is\(span\,div\)_\&\]\:p-1:hover,
- div .\[div_\&\]\:p-1,
- div .hover\:\[div_\&\]\:p-1:hover {
- padding: 0.25rem;
- }
- `)
- })
- })
- it('should sort multiple variant fns with normal variants between them', () => {
- /** @type {string[]} */
- let lines = []
- for (let a of [1, 2]) {
- for (let b of [2, 1]) {
- for (let c of [1, 2]) {
- for (let d of [2, 1]) {
- for (let e of [1, 2]) {
- lines.push(`<div class="fred${a}:qux-[${b}]:baz${c}:bar-[${d}]:foo${e}:p-1"></div>`)
- }
- }
- }
- }
- }
- // Fisher-Yates shuffle
- for (let i = lines.length - 1; i > 0; i--) {
- let j = Math.floor(Math.random() * i)
- ;[lines[i], lines[j]] = [lines[j], lines[i]]
- }
- let config = {
- content: [
- {
- raw: lines.join('\n'),
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- function ({ addVariant, matchVariant }) {
- addVariant('foo1', "&[data-foo='1']")
- addVariant('foo2', "&[data-foo='2']")
- matchVariant('bar', (value) => `&[data-bar='${value}']`, {
- sort: (a, b) => b.value - a.value,
- })
- addVariant('baz1', "&[data-baz='1']")
- addVariant('baz2', "&[data-baz='2']")
- matchVariant('qux', (value) => `&[data-qux='${value}']`, {
- sort: (a, b) => b.value - a.value,
- })
- addVariant('fred1', "&[data-fred='1']")
- addVariant('fred2', "&[data-fred='2']")
- },
- ],
- }
- let input = css`
- @tailwind utilities;
- `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .fred1\:qux-\[2\]\:baz1\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='1'][data-qux='2'][data-fred='1'],
- .fred1\:qux-\[2\]\:baz1\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='1'][data-qux='2'][data-fred='1'],
- .fred1\:qux-\[2\]\:baz1\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='1'][data-qux='2'][data-fred='1'],
- .fred1\:qux-\[2\]\:baz1\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='1'][data-qux='2'][data-fred='1'],
- .fred1\:qux-\[2\]\:baz2\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='2'][data-qux='2'][data-fred='1'],
- .fred1\:qux-\[2\]\:baz2\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='2'][data-qux='2'][data-fred='1'],
- .fred1\:qux-\[2\]\:baz2\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='2'][data-qux='2'][data-fred='1'],
- .fred1\:qux-\[2\]\:baz2\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='2'][data-qux='2'][data-fred='1'],
- .fred1\:qux-\[1\]\:baz1\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='1'][data-qux='1'][data-fred='1'],
- .fred1\:qux-\[1\]\:baz1\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='1'][data-qux='1'][data-fred='1'],
- .fred1\:qux-\[1\]\:baz1\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='1'][data-qux='1'][data-fred='1'],
- .fred1\:qux-\[1\]\:baz1\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='1'][data-qux='1'][data-fred='1'],
- .fred1\:qux-\[1\]\:baz2\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='2'][data-qux='1'][data-fred='1'],
- .fred1\:qux-\[1\]\:baz2\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='2'][data-qux='1'][data-fred='1'],
- .fred1\:qux-\[1\]\:baz2\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='2'][data-qux='1'][data-fred='1'],
- .fred1\:qux-\[1\]\:baz2\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='2'][data-qux='1'][data-fred='1'],
- .fred2\:qux-\[2\]\:baz1\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='1'][data-qux='2'][data-fred='2'],
- .fred2\:qux-\[2\]\:baz1\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='1'][data-qux='2'][data-fred='2'],
- .fred2\:qux-\[2\]\:baz1\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='1'][data-qux='2'][data-fred='2'],
- .fred2\:qux-\[2\]\:baz1\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='1'][data-qux='2'][data-fred='2'],
- .fred2\:qux-\[2\]\:baz2\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='2'][data-qux='2'][data-fred='2'],
- .fred2\:qux-\[2\]\:baz2\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='2'][data-qux='2'][data-fred='2'],
- .fred2\:qux-\[2\]\:baz2\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='2'][data-qux='2'][data-fred='2'],
- .fred2\:qux-\[2\]\:baz2\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='2'][data-qux='2'][data-fred='2'],
- .fred2\:qux-\[1\]\:baz1\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='1'][data-qux='1'][data-fred='2'],
- .fred2\:qux-\[1\]\:baz1\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='1'][data-qux='1'][data-fred='2'],
- .fred2\:qux-\[1\]\:baz1\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='1'][data-qux='1'][data-fred='2'],
- .fred2\:qux-\[1\]\:baz1\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='1'][data-qux='1'][data-fred='2'],
- .fred2\:qux-\[1\]\:baz2\:bar-\[2\]\:foo1\:p-1[data-foo='1'][data-bar='2'][data-baz='2'][data-qux='1'][data-fred='2'],
- .fred2\:qux-\[1\]\:baz2\:bar-\[2\]\:foo2\:p-1[data-foo='2'][data-bar='2'][data-baz='2'][data-qux='1'][data-fred='2'],
- .fred2\:qux-\[1\]\:baz2\:bar-\[1\]\:foo1\:p-1[data-foo='1'][data-bar='1'][data-baz='2'][data-qux='1'][data-fred='2'],
- .fred2\:qux-\[1\]\:baz2\:bar-\[1\]\:foo2\:p-1[data-foo='2'][data-bar='1'][data-baz='2'][data-qux='1'][data-fred='2'] {
- padding: 0.25rem;
- }
- `)
- })
- })
|