import { run, html, css, defaults } from './util/run' test('basic arbitrary variants', () => { let config = { content: [{ raw: html`
` }], 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`
`, }, ], 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`
` }], 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`
`, }, ], 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`
` }], 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`
` }], 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`
` }], 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`
`, }, ], 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`
` }], 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`
`, }, ], 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`
` }], 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`
` }], 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`
` }, ], 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`
` }, ], 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`
`, }, ], 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: '
', }, ], corePlugins: { preflight: false }, } let input = ` @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: '
', }, ], corePlugins: { preflight: false }, } let input = ` @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: `
`, }, ], corePlugins: { preflight: false }, } let input = ` @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 { text-decoration-line: underline; } .hover\:\[\&_\.foo\\_\\_bar\]\:underline .foo__bar:hover { text-decoration-line: underline; } `) }) }) test('allows attribute variants with quotes', () => { let config = { content: [ { raw: `
`, }, ], corePlugins: { preflight: false }, } let input = ` @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"] { text-decoration-line: underline; } .\[\&\[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: `
should not be red
should be red
should not be red
should be red
`, }, ], corePlugins: { preflight: false }, } let input = ` @tailwind utilities; ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` .foo .\[\.foo_\&\]\:tw-text-red-400 { --tw-text-opacity: 1; color: rgb(248 113 113 / var(--tw-text-opacity)); } .\[\&_\.foo\]\:tw-text-red-400 .foo { --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: `
should not be red
should be red
should not be red
should be red
`, }, ], corePlugins: { preflight: false }, } let input = ` @tailwind utilities; ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` .foo .\[\.foo_\&\]\:tw-bg-white { --tw-bg-opacity: 1; background-color: rgb(255 255 255 / var(--tw-bg-opacity)); } .foo .\[\.foo_\&\]\:tw-text-red-400 { --tw-text-opacity: 1; color: rgb(248 113 113 / var(--tw-text-opacity)); } .\[\&_\.foo\]\:tw-bg-white .foo { --tw-bg-opacity: 1; background-color: rgb(255 255 255 / var(--tw-bg-opacity)); } .\[\&_\.foo\]\:tw-text-red-400 .foo { --tw-text-opacity: 1; color: rgb(248 113 113 / var(--tw-text-opacity)); } `) }) }) it('should support aria variants', () => { let config = { content: [ { raw: html`
`, }, ], corePlugins: { preflight: false }, } let input = css` @tailwind utilities; ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` .aria-checked\:underline[aria-checked='true'] { text-decoration-line: underline; } .aria-\[sort\=ascending\]\:underline[aria-sort='ascending'] { text-decoration-line: underline; } .aria-\[labelledby\=a_b\]\:underline[aria-labelledby='a b'] { text-decoration-line: underline; } .group[aria-checked='true'] .group-aria-checked\:underline { text-decoration-line: underline; } .group\/foo[aria-checked='true'] .group-aria-checked\/foo\:underline { text-decoration-line: underline; } .group[aria-sort='ascending'] .group-aria-\[sort\=ascending\]\:underline { text-decoration-line: underline; } .group[aria-labelledby='a b'] .group-aria-\[labelledby\=a_b\]\:underline { text-decoration-line: underline; } .group\/foo[aria-sort='ascending'] .group-aria-\[sort\=ascending\]\/foo\:underline { text-decoration-line: underline; } .peer[aria-checked='true'] ~ .peer-aria-checked\:underline { text-decoration-line: underline; } .peer\/foo[aria-checked='true'] ~ .peer-aria-checked\/foo\:underline { text-decoration-line: underline; } .peer[aria-sort='ascending'] ~ .peer-aria-\[sort\=ascending\]\:underline { text-decoration-line: underline; } .peer[aria-labelledby='a b'] ~ .peer-aria-\[labelledby\=a_b\]\:underline { text-decoration-line: underline; } .peer\/foo[aria-sort='ascending'] ~ .peer-aria-\[sort\=ascending\]\/foo\:underline { text-decoration-line: underline; } `) }) }) it('should support data variants', () => { let config = { theme: { data: { checked: 'ui~="checked"', }, }, content: [ { raw: html`
`, }, ], corePlugins: { preflight: false }, } let input = css` @tailwind utilities; ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` .data-checked\:underline[data-ui~='checked'] { text-decoration-line: underline; } .data-\[position\=top\]\:underline[data-position='top'] { text-decoration-line: underline; } .data-\[foo\=bar_baz\]\:underline[data-foo='bar baz'] { text-decoration-line: underline; } .group[data-ui~='checked'] .group-data-checked\:underline { text-decoration-line: underline; } .group\/foo[data-ui~='checked'] .group-data-checked\/foo\:underline { text-decoration-line: underline; } .group[data-position='top'] .group-data-\[position\=top\]\:underline { text-decoration-line: underline; } .group[data-foo='bar baz'] .group-data-\[foo\=bar_baz\]\:underline { text-decoration-line: underline; } .group\/foo[data-position='top'] .group-data-\[position\=top\]\/foo\:underline { text-decoration-line: underline; } .peer[data-ui~='checked'] ~ .peer-data-checked\:underline { text-decoration-line: underline; } .peer\/foo[data-ui~='checked'] ~ .peer-data-checked\/foo\:underline { text-decoration-line: underline; } .peer[data-position='top'] ~ .peer-data-\[position\=top\]\:underline { text-decoration-line: underline; } .peer[data-foo='bar baz'] ~ .peer-data-\[foo\=bar_baz\]\:underline { text-decoration-line: underline; } .peer\/foo[data-position='top'] ~ .peer-data-\[position\=top\]\/foo\:underline { text-decoration-line: underline; } `) }) }) it('should support supports', () => { let config = { theme: { supports: { grid: 'display: grid', }, }, content: [ { raw: html`
`, }, ], 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) { .supports-\[display\:grid\]\:grid { display: grid; } } @supports (transform-origin: 5% 5%) { .supports-\[transform-origin\:5\%_5\%\]\:underline { text-decoration-line: underline; } } @supports selector(A > B) { .supports-\[selector\(A\>B\)\]\:underline { text-decoration-line: underline; } } @supports not (foo: bar) { .supports-\[not\(foo\:bar\)\]\:underline { text-decoration-line: underline; } } @supports (foo: bar) or (bar: baz) { .supports-\[\(foo\:bar\)or\(bar\:baz\)\]\:underline { text-decoration-line: underline; } } @supports (foo: bar) and (bar: baz) { .supports-\[\(foo\:bar\)and\(bar\:baz\)\]\:underline { text-decoration-line: underline; } } @supports (container-type: var(--tw)) { .supports-\[container-type\]\:underline { text-decoration-line: underline; } } `) }) }) it('should be possible to use modifiers and arbitrary groups', () => { let config = { content: [ { raw: html`
`, }, ], corePlugins: { preflight: false }, } let input = css` @tailwind utilities; ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` .group:hover .group-hover\:underline { text-decoration-line: underline; } .group\/foo:hover .group-hover\/foo\:underline { text-decoration-line: underline; } .group:focus .group-\[\&\:focus\]\:underline { text-decoration-line: underline; } .group:hover .group-\[\:hover\]\:underline { text-decoration-line: underline; } .group[data-open] .group-\[\&\[data-open\]\]\:underline { text-decoration-line: underline; } .group[data-open] .group-\[\[data-open\]\]\:underline { text-decoration-line: underline; } .in-foo .group .group-\[\.in-foo_\&\]\:underline { text-decoration-line: underline; } .group.in-foo .group-\[\.in-foo\]\:underline { text-decoration-line: underline; } .group\/foo:focus .group-\[\&\:focus\]\/foo\:underline { text-decoration-line: underline; } .group\/foo:hover .group-\[\:hover\]\/foo\:underline { text-decoration-line: underline; } .group\/foo[data-open] .group-\[\&\[data-open\]\]\/foo\:underline { text-decoration-line: underline; } .group\/foo[data-open] .group-\[\[data-open\]\]\/foo\:underline { text-decoration-line: underline; } .in-foo .group\/foo .group-\[\.in-foo_\&\]\/foo\:underline { text-decoration-line: underline; } .group\/foo.in-foo .group-\[\.in-foo\]\/foo\:underline { text-decoration-line: underline; } `) }) }) it('should be possible to use modifiers and arbitrary peers', () => { let config = { content: [ { raw: html`
`, }, ], corePlugins: { preflight: false }, } let input = css` @tailwind utilities; ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` .peer:hover ~ .peer-hover\:underline { text-decoration-line: underline; } .peer\/foo:hover ~ .peer-hover\/foo\:underline { text-decoration-line: underline; } .peer:focus ~ .peer-\[\&\:focus\]\:underline { text-decoration-line: underline; } .peer:hover ~ .peer-\[\:hover\]\:underline { text-decoration-line: underline; } .peer[data-open] ~ .peer-\[\&\[data-open\]\]\:underline { text-decoration-line: underline; } .peer[data-open] ~ .peer-\[\[data-open\]\]\:underline { text-decoration-line: underline; } .in-foo .peer ~ .peer-\[\.in-foo_\&\]\:underline { text-decoration-line: underline; } .peer.in-foo ~ .peer-\[\.in-foo\]\:underline { text-decoration-line: underline; } .peer\/foo:focus ~ .peer-\[\&\:focus\]\/foo\:underline { text-decoration-line: underline; } .peer\/foo:hover ~ .peer-\[\:hover\]\/foo\:underline { text-decoration-line: underline; } .peer\/foo[data-open] ~ .peer-\[\&\[data-open\]\]\/foo\:underline { text-decoration-line: underline; } .peer\/foo[data-open] ~ .peer-\[\[data-open\]\]\/foo\:underline { text-decoration-line: underline; } .in-foo .peer\/foo ~ .peer-\[\.in-foo_\&\]\/foo\:underline { text-decoration-line: underline; } .peer\/foo.in-foo ~ .peer-\[\.in-foo\]\/foo\:underline { text-decoration-line: underline; } `) }) })