+
should not be red
should be red
`,
- },
- ],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .\[\&_\.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));
- }
- .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));
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .\[\&_\.foo\]\:tw-bg-white .foo {
- background-color: #fff;
- }
- .\[\&_\.foo\]\:tw-text-red-400 .foo {
- color: #f87171;
- }
- .foo .\[\.foo_\&\]\:tw-bg-white {
- background-color: #fff;
- }
- .foo .\[\.foo_\&\]\:tw-text-red-400 {
- color: #f87171;
- }
- `)
- })
- })
-
- it('should support aria variants', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(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;
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(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;
- }
- `)
- })
- })
-
- it('should support data variants', () => {
- let config = {
- theme: {
- data: {
- checked: 'ui~="checked"',
- },
},
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
+ ],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(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;
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(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;
- }
- `)
- })
+ 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 supports', () => {
- let config = {
- theme: {
- supports: {
- grid: 'display: grid',
- },
+it('should support aria variants', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
},
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
+ ],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind utilities;
- `
+ 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;
- }
- }
- `)
- })
+ 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 be possible to use modifiers and arbitrary groups', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
-
+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(
+ 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`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
-
-
-
+ let input = css`
+ @tailwind utilities;
+ `
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- 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 {
+ 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;
+ }
+ }
+ `)
})
+})
- it('should be possible to use modifiers and arbitrary peers', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
+test('has-* variants with arbitrary values', () => {
+ let config = {
+ theme: {},
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ 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`
+
+
+ `,
+ },
+ ],
+ 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`
+
+
+ `,
+ },
+ ],
+ 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`
+
+
+
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
- `,
- },
- ],
- 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`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind utilities;
- `
+ 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;
- }
- `)
- })
+ 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('Arbitrary variants support multiple attribute selectors', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: { preflight: false },
- }
+it('should be possible to use modifiers and arbitrary peers', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
- 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;
- }
- `)
- })
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ 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('Invalid arbitrary variants selectors should produce nothing instead of failing', () => {
- let config = {
- content: [
- {
- raw: html`
+it('Arbitrary variants are ordered alphabetically', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ 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`
`,
+ },
+ ],
+ 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`
`,
- },
- ],
- corePlugins: { preflight: false },
- }
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css``)
- })
+ 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`
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
+it('should output responsive variants + stacked variants in the right order', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind utilities;
- `
+ 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`
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- {
- // escaped commas are a-ok
- // This is separate because prettier complains about `\,` in the template string
- raw: '
',
- },
- ],
- 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 {
+ 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('should sort multiple variant fns with normal variants between them', () => {
- /** @type {string[]} */
- let lines = []
+it('it should discard arbitrary variants with multiple selectors', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ {
+ // escaped commas are a-ok
+ // This is separate because prettier complains about `\,` in the template string
+ raw: '
',
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
- 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(`
`)
- }
+ 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(`
`)
}
}
}
}
+ }
- // 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]]
- }
+ // 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']")
+ 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,
- })
+ matchVariant('bar', (value) => `&[data-bar='${value}']`, {
+ sort: (a, b) => b.value - a.value,
+ })
- addVariant('baz1', "&[data-baz='1']")
- addVariant('baz2', "&[data-baz='2']")
+ addVariant('baz1', "&[data-baz='1']")
+ addVariant('baz2', "&[data-baz='2']")
- matchVariant('qux', (value) => `&[data-qux='${value}']`, {
- sort: (a, b) => b.value - a.value,
- })
+ matchVariant('qux', (value) => `&[data-qux='${value}']`, {
+ sort: (a, b) => b.value - a.value,
+ })
- addVariant('fred1', "&[data-fred='1']")
- addVariant('fred2', "&[data-fred='2']")
- },
- ],
- }
+ addVariant('fred1', "&[data-fred='1']")
+ addVariant('fred2', "&[data-fred='2']")
+ },
+ ],
+ }
- let input = css`
- @tailwind utilities;
- `
+ 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;
- }
- `)
- })
+ 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;
+ }
+ `)
})
})
diff --git a/tests/basic-usage.oxide.test.css b/tests/basic-usage.oxide.test.css
deleted file mode 100644
index 6a01dc045..000000000
--- a/tests/basic-usage.oxide.test.css
+++ /dev/null
@@ -1,1028 +0,0 @@
-*,
-:before,
-:after,
-::backdrop {
- --tw-border-spacing-x: 0;
- --tw-border-spacing-y: 0;
- --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-pan-x: ;
- --tw-pan-y: ;
- --tw-pinch-zoom: ;
- --tw-scroll-snap-strictness: proximity;
- --tw-gradient-from-position: ;
- --tw-gradient-via-position: ;
- --tw-gradient-to-position: ;
- --tw-ordinal: ;
- --tw-slashed-zero: ;
- --tw-numeric-figure: ;
- --tw-numeric-spacing: ;
- --tw-numeric-fraction: ;
- --tw-ring-inset: ;
- --tw-ring-offset-width: 0px;
- --tw-ring-offset-color: #fff;
- --tw-ring-color: #3b82f680;
- --tw-ring-offset-shadow: 0 0 #0000;
- --tw-ring-shadow: 0 0 #0000;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- --tw-blur: ;
- --tw-brightness: ;
- --tw-contrast: ;
- --tw-grayscale: ;
- --tw-hue-rotate: ;
- --tw-invert: ;
- --tw-saturate: ;
- --tw-sepia: ;
- --tw-drop-shadow: ;
- --tw-backdrop-blur: ;
- --tw-backdrop-brightness: ;
- --tw-backdrop-contrast: ;
- --tw-backdrop-grayscale: ;
- --tw-backdrop-hue-rotate: ;
- --tw-backdrop-invert: ;
- --tw-backdrop-opacity: ;
- --tw-backdrop-saturate: ;
- --tw-backdrop-sepia: ;
-}
-.container {
- width: 100%;
-}
-@media (min-width: 640px) {
- .container {
- max-width: 640px;
- }
-}
-@media (min-width: 768px) {
- .container {
- max-width: 768px;
- }
-}
-@media (min-width: 1024px) {
- .container {
- max-width: 1024px;
- }
-}
-@media (min-width: 1280px) {
- .container {
- max-width: 1280px;
- }
-}
-@media (min-width: 1536px) {
- .container {
- max-width: 1536px;
- }
-}
-.sr-only {
- width: 1px;
- height: 1px;
- clip: rect(0, 0, 0, 0);
- white-space: nowrap;
- border-width: 0;
- margin: -1px;
- padding: 0;
- position: absolute;
- overflow: hidden;
-}
-.pointer-events-none {
- pointer-events: none;
-}
-.invisible {
- visibility: hidden;
-}
-.collapse {
- visibility: collapse;
-}
-.absolute {
- position: absolute;
-}
-.inset-0 {
- inset: 0;
-}
-.inset-x-2 {
- left: 0.5rem;
- right: 0.5rem;
-}
-.inset-y-4 {
- top: 1rem;
- bottom: 1rem;
-}
-.bottom-12 {
- bottom: 3rem;
-}
-.left-16 {
- left: 4rem;
-}
-.right-8 {
- right: 2rem;
-}
-.top-6 {
- top: 1.5rem;
-}
-.isolate {
- isolation: isolate;
-}
-.isolation-auto {
- isolation: auto;
-}
-.z-30 {
- z-index: 30;
-}
-.order-2 {
- order: 2;
-}
-.order-last {
- order: 9999;
-}
-.col-span-3 {
- grid-column: span 3 / span 3;
-}
-.col-start-1 {
- grid-column-start: 1;
-}
-.col-end-4 {
- grid-column-end: 4;
-}
-.row-span-2 {
- grid-row: span 2 / span 2;
-}
-.row-start-3 {
- grid-row-start: 3;
-}
-.row-end-5 {
- grid-row-end: 5;
-}
-.float-right {
- float: right;
-}
-.clear-left {
- clear: left;
-}
-.m-4 {
- margin: 1rem;
-}
-.mx-auto {
- margin-left: auto;
- margin-right: auto;
-}
-.my-2 {
- margin-top: 0.5rem;
- margin-bottom: 0.5rem;
-}
-.mb-3 {
- margin-bottom: 0.75rem;
-}
-.ml-4 {
- margin-left: 1rem;
-}
-.mr-1 {
- margin-right: 0.25rem;
-}
-.mt-0 {
- margin-top: 0;
-}
-.box-border {
- box-sizing: border-box;
-}
-.inline-grid {
- display: inline-grid;
-}
-.hidden {
- display: none;
-}
-.aspect-square {
- aspect-ratio: 1 / 1;
-}
-.aspect-video {
- aspect-ratio: 16 / 9;
-}
-.h-16 {
- height: 4rem;
-}
-.max-h-screen {
- max-height: 100vh;
-}
-.min-h-0 {
- min-height: 0;
-}
-.w-12 {
- width: 3rem;
-}
-.min-w-min {
- min-width: min-content;
-}
-.max-w-full {
- max-width: 100%;
-}
-.flex-1 {
- flex: 1;
-}
-.flex-shrink {
- flex-shrink: 1;
-}
-.flex-shrink-0 {
- flex-shrink: 0;
-}
-.shrink {
- flex-shrink: 1;
-}
-.shrink-0 {
- flex-shrink: 0;
-}
-.flex-grow {
- flex-grow: 1;
-}
-.flex-grow-0 {
- flex-grow: 0;
-}
-.grow {
- flex-grow: 1;
-}
-.grow-0 {
- flex-grow: 0;
-}
-.basis-7 {
- flex-basis: 1.75rem;
-}
-.basis-auto {
- flex-basis: auto;
-}
-.table-fixed {
- table-layout: fixed;
-}
-.caption-top {
- caption-side: top;
-}
-.caption-bottom {
- caption-side: bottom;
-}
-.border-collapse {
- border-collapse: collapse;
-}
-.border-spacing-4 {
- --tw-border-spacing-x: 1rem;
- --tw-border-spacing-y: 1rem;
- border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y);
-}
-.border-spacing-x-6 {
- --tw-border-spacing-x: 1.5rem;
- border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y);
-}
-.border-spacing-y-8 {
- --tw-border-spacing-y: 2rem;
- border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y);
-}
-.origin-top-right {
- transform-origin: 100% 0;
-}
-.-translate-x-3 {
- --tw-translate-x: -0.75rem;
- transform: translate(var(--tw-translate-x), 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));
-}
-.-translate-x-4 {
- --tw-translate-x: -1rem;
- transform: translate(var(--tw-translate-x), 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));
-}
-.translate-x-5 {
- --tw-translate-x: 1.25rem;
- transform: translate(var(--tw-translate-x), 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));
-}
-.translate-y-6 {
- --tw-translate-y: 1.5rem;
- transform: translate(var(--tw-translate-x), 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: translate(var(--tw-translate-x), 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));
-}
-.skew-x-12 {
- --tw-skew-x: 12deg;
- transform: translate(var(--tw-translate-x), 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));
-}
-.skew-y-12 {
- --tw-skew-y: 12deg;
- transform: translate(var(--tw-translate-x), 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));
-}
-.scale-95 {
- --tw-scale-x: 0.95;
- --tw-scale-y: 0.95;
- transform: translate(var(--tw-translate-x), 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));
-}
-.-scale-x-100 {
- --tw-scale-x: -1;
- transform: translate(var(--tw-translate-x), 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));
-}
-.transform {
- transform: translate(var(--tw-translate-x), 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));
-}
-.transform-gpu {
- transform: translate3d(var(--tw-translate-x), var(--tw-translate-y), 0) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
-}
-.transform-none {
- transform: none;
-}
-.animate-none {
- animation: none;
-}
-@keyframes spin {
- to {
- transform: rotate(360deg);
- }
-}
-.animate-spin {
- animation: 1s linear infinite spin;
-}
-.cursor-pointer {
- cursor: pointer;
-}
-.touch-pan-x {
- --tw-pan-x: pan-x;
- touch-action: var(--tw-pan-x) var(--tw-pan-y) var(--tw-pinch-zoom);
-}
-.touch-pan-y {
- --tw-pan-y: pan-y;
- touch-action: var(--tw-pan-x) var(--tw-pan-y) var(--tw-pinch-zoom);
-}
-.touch-pan-up {
- --tw-pan-y: pan-up;
- touch-action: var(--tw-pan-x) var(--tw-pan-y) var(--tw-pinch-zoom);
-}
-.touch-pinch-zoom {
- --tw-pinch-zoom: pinch-zoom;
- touch-action: var(--tw-pan-x) var(--tw-pan-y) var(--tw-pinch-zoom);
-}
-.touch-manipulation {
- touch-action: manipulation;
-}
-.select-none {
- user-select: none;
-}
-.resize-none {
- resize: none;
-}
-.snap-x {
- scroll-snap-type: x var(--tw-scroll-snap-strictness);
-}
-.snap-mandatory {
- --tw-scroll-snap-strictness: mandatory;
-}
-.snap-center {
- scroll-snap-align: center;
-}
-.snap-always {
- scroll-snap-stop: always;
-}
-.scroll-mt-6 {
- scroll-margin-top: 1.5rem;
-}
-.scroll-p-6 {
- scroll-padding: 1.5rem;
-}
-.list-inside {
- list-style-position: inside;
-}
-.list-disc {
- list-style-type: disc;
-}
-.list-image-none {
- list-style-image: none;
-}
-.appearance-none {
- appearance: none;
-}
-.columns-1 {
- columns: 1;
-}
-.columns-md {
- columns: 28rem;
-}
-.break-before-page {
- break-before: page;
-}
-.break-inside-avoid-column {
- break-inside: avoid-column;
-}
-.break-after-auto {
- break-after: auto;
-}
-.auto-cols-min {
- grid-auto-columns: min-content;
-}
-.grid-flow-row {
- grid-auto-flow: row;
-}
-.auto-rows-max {
- grid-auto-rows: max-content;
-}
-.grid-cols-4 {
- grid-template-columns: repeat(4, minmax(0, 1fr));
-}
-.grid-rows-3 {
- grid-template-rows: repeat(3, minmax(0, 1fr));
-}
-.flex-row-reverse {
- flex-direction: row-reverse;
-}
-.flex-wrap {
- flex-wrap: wrap;
-}
-.place-content-start {
- place-content: start;
-}
-.place-items-end {
- place-items: end;
-}
-.content-center {
- align-content: center;
-}
-.items-start {
- align-items: flex-start;
-}
-.justify-center {
- justify-content: center;
-}
-.justify-items-end {
- justify-items: end;
-}
-.gap-4 {
- gap: 1rem;
-}
-.gap-x-2 {
- column-gap: 0.5rem;
-}
-.gap-y-3 {
- row-gap: 0.75rem;
-}
-.space-x-4 > :not([hidden]) ~ :not([hidden]) {
- --tw-space-x-reverse: 0;
- margin-inline-start: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
- margin-inline-end: calc(1rem * var(--tw-space-x-reverse));
-}
-.space-y-3 > :not([hidden]) ~ :not([hidden]) {
- --tw-space-y-reverse: 0;
- margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse)));
- margin-bottom: calc(0.75rem * var(--tw-space-y-reverse));
-}
-.space-y-reverse > :not([hidden]) ~ :not([hidden]) {
- --tw-space-y-reverse: 1;
-}
-.space-x-reverse > :not([hidden]) ~ :not([hidden]) {
- --tw-space-x-reverse: 1;
-}
-.divide-x-0 > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-x-reverse: 0;
- border-inline-end-width: calc(0px * var(--tw-divide-x-reverse));
- border-inline-start-width: calc(0px * calc(1 - var(--tw-divide-x-reverse)));
-}
-.divide-x-2 > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-x-reverse: 0;
- border-inline-end-width: calc(2px * var(--tw-divide-x-reverse));
- border-inline-start-width: calc(2px * calc(1 - var(--tw-divide-x-reverse)));
-}
-.divide-y-0 > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-y-reverse: 0;
- border-top-width: calc(0px * calc(1 - var(--tw-divide-y-reverse)));
- border-bottom-width: calc(0px * var(--tw-divide-y-reverse));
-}
-.divide-y-4 > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-y-reverse: 0;
- border-top-width: calc(4px * calc(1 - var(--tw-divide-y-reverse)));
- border-bottom-width: calc(4px * var(--tw-divide-y-reverse));
-}
-.divide-dotted > :not([hidden]) ~ :not([hidden]) {
- border-style: dotted;
-}
-.divide-gray-200 > :not([hidden]) ~ :not([hidden]) {
- border-color: #e5e7eb;
-}
-.place-self-center {
- place-self: center;
-}
-.self-end {
- align-self: flex-end;
-}
-.justify-self-start {
- justify-self: start;
-}
-.overflow-hidden {
- overflow: hidden;
-}
-.overscroll-contain {
- overscroll-behavior: contain;
-}
-.truncate {
- text-overflow: ellipsis;
- white-space: nowrap;
- overflow: hidden;
-}
-.overflow-ellipsis {
- text-overflow: ellipsis;
-}
-.whitespace-nowrap {
- white-space: nowrap;
-}
-.break-words {
- overflow-wrap: break-word;
-}
-.rounded-md {
- border-radius: 0.375rem;
-}
-.border {
- border-width: 1px;
-}
-.border-2 {
- border-width: 2px;
-}
-.border-x-4 {
- border-left-width: 4px;
- border-right-width: 4px;
-}
-.border-y-4 {
- border-top-width: 4px;
- border-bottom-width: 4px;
-}
-.border-b-4 {
- border-bottom-width: 4px;
-}
-.border-t {
- border-top-width: 1px;
-}
-.border-solid {
- border-style: solid;
-}
-.border-hidden {
- border-style: hidden;
-}
-.border-black {
- border-color: #000;
-}
-.border-x-black {
- border-left-color: #000;
- border-right-color: #000;
-}
-.border-y-black {
- border-top-color: #000;
- border-bottom-color: #000;
-}
-.border-b-black {
- border-bottom-color: #000;
-}
-.border-l-black {
- border-left-color: #000;
-}
-.border-r-black {
- border-right-color: #000;
-}
-.border-t-black {
- border-top-color: #000;
-}
-.bg-green-500 {
- background-color: #22c55e;
-}
-.bg-gradient-to-r {
- background-image: linear-gradient(to right, var(--tw-gradient-stops));
-}
-.from-red-300 {
- --tw-gradient-from: #fca5a5 var(--tw-gradient-from-position);
- --tw-gradient-to: #fca5a500 var(--tw-gradient-to-position);
- --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
-}
-.via-purple-200 {
- --tw-gradient-to: #e9d5ff00 var(--tw-gradient-to-position);
- --tw-gradient-stops: var(--tw-gradient-from), #e9d5ff var(--tw-gradient-via-position),
- var(--tw-gradient-to);
-}
-.to-blue-400 {
- --tw-gradient-to: #60a5fa var(--tw-gradient-to-position);
-}
-.decoration-slice {
- -webkit-box-decoration-break: slice;
- box-decoration-break: slice;
-}
-.decoration-clone {
- -webkit-box-decoration-break: clone;
- box-decoration-break: clone;
-}
-.box-decoration-slice {
- -webkit-box-decoration-break: slice;
- box-decoration-break: slice;
-}
-.box-decoration-clone {
- -webkit-box-decoration-break: clone;
- box-decoration-break: clone;
-}
-.bg-cover {
- background-size: cover;
-}
-.bg-local {
- background-attachment: local;
-}
-.bg-clip-border {
- background-clip: border-box;
-}
-.bg-top {
- background-position: top;
-}
-.bg-no-repeat {
- background-repeat: no-repeat;
-}
-.bg-origin-border {
- background-origin: border-box;
-}
-.bg-origin-padding {
- background-origin: padding-box;
-}
-.bg-origin-content {
- background-origin: content-box;
-}
-.fill-current {
- fill: currentColor;
-}
-.stroke-current {
- stroke: currentColor;
-}
-.stroke-2 {
- stroke-width: 2px;
-}
-.object-cover {
- object-fit: cover;
-}
-.object-bottom {
- object-position: bottom;
-}
-.p-4 {
- padding: 1rem;
-}
-.px-3 {
- padding-left: 0.75rem;
- padding-right: 0.75rem;
-}
-.py-2 {
- padding-top: 0.5rem;
- padding-bottom: 0.5rem;
-}
-.pb-3 {
- padding-bottom: 0.75rem;
-}
-.pl-4 {
- padding-left: 1rem;
-}
-.pr-2 {
- padding-right: 0.5rem;
-}
-.pt-1 {
- padding-top: 0.25rem;
-}
-.text-center {
- text-align: center;
-}
-.-indent-12 {
- text-indent: -3rem;
-}
-.indent-6 {
- text-indent: 1.5rem;
-}
-.align-middle {
- vertical-align: middle;
-}
-.font-sans {
- font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
- Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol,
- Noto Color Emoji;
-}
-.text-2xl {
- font-size: 1.5rem;
- line-height: 2rem;
-}
-.font-medium {
- font-weight: 500;
-}
-.uppercase {
- text-transform: uppercase;
-}
-.not-italic {
- font-style: normal;
-}
-.ordinal {
- --tw-ordinal: ordinal;
- font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
- var(--tw-numeric-spacing) var(--tw-numeric-fraction);
-}
-.tabular-nums {
- --tw-numeric-spacing: tabular-nums;
- font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
- var(--tw-numeric-spacing) var(--tw-numeric-fraction);
-}
-.diagonal-fractions {
- --tw-numeric-fraction: diagonal-fractions;
- font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
- var(--tw-numeric-spacing) var(--tw-numeric-fraction);
-}
-.leading-5 {
- line-height: 1.25rem;
-}
-.leading-relaxed {
- line-height: 1.625;
-}
-.tracking-tight {
- letter-spacing: -0.025em;
-}
-.text-indigo-500 {
- color: #6366f1;
-}
-.underline {
- text-decoration-line: underline;
-}
-.decoration-red-600 {
- text-decoration-color: #dc2626;
-}
-.decoration-solid {
- text-decoration-style: solid;
-}
-.decoration-1 {
- text-decoration-thickness: 1px;
-}
-.decoration-2 {
- text-decoration-thickness: 2px;
-}
-.underline-offset-1 {
- text-underline-offset: 1px;
-}
-.underline-offset-2 {
- text-underline-offset: 2px;
-}
-.antialiased {
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-.placeholder-green-300::placeholder {
- color: #86efac;
-}
-.caret-red-600 {
- caret-color: #dc2626;
-}
-.accent-red-600 {
- accent-color: #dc2626;
-}
-.opacity-90 {
- opacity: 0.9;
-}
-.bg-blend-darken {
- background-blend-mode: darken;
-}
-.bg-blend-difference {
- background-blend-mode: difference;
-}
-.mix-blend-multiply {
- mix-blend-mode: multiply;
-}
-.mix-blend-saturation {
- mix-blend-mode: saturation;
-}
-.shadow {
- --tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
- --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
-}
-.shadow-lg {
- --tw-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;
- --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color),
- 0 4px 6px -4px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
-}
-.shadow-md {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
-}
-.shadow-black {
- --tw-shadow-color: #000;
- --tw-shadow: var(--tw-shadow-colored);
-}
-.shadow-blue-100\/10 {
- --tw-shadow-color: #dbeafe1a;
- --tw-shadow: var(--tw-shadow-colored);
-}
-.shadow-red-500\/25 {
- --tw-shadow-color: #ef444440;
- --tw-shadow: var(--tw-shadow-colored);
-}
-.outline-none {
- outline-offset: 2px;
- outline: 2px solid #0000;
-}
-.outline {
- outline-style: solid;
-}
-.outline-dashed {
- outline-style: dashed;
-}
-.outline-4 {
- outline-width: 4px;
-}
-.-outline-offset-2 {
- outline-offset: -2px;
-}
-.outline-offset-2 {
- outline-offset: 2px;
-}
-.outline-black {
- outline-color: #000;
-}
-.ring {
- --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(3px + 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-4 {
- --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(4px + 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-white {
- --tw-ring-color: #fff;
-}
-.ring-offset-2 {
- --tw-ring-offset-width: 2px;
-}
-.ring-offset-blue-300 {
- --tw-ring-offset-color: #93c5fd;
-}
-.blur-md {
- --tw-blur: blur(12px);
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale)
- var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-.brightness-150 {
- --tw-brightness: brightness(1.5);
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale)
- var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-.contrast-50 {
- --tw-contrast: contrast(0.5);
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale)
- var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-.drop-shadow-md {
- --tw-drop-shadow: drop-shadow(0 4px 3px #00000012) drop-shadow(0 2px 2px #0000000f);
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale)
- var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-.grayscale {
- --tw-grayscale: grayscale(100%);
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale)
- var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-.hue-rotate-60 {
- --tw-hue-rotate: hue-rotate(60deg);
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale)
- var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-.invert {
- --tw-invert: invert(100%);
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale)
- var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-.saturate-200 {
- --tw-saturate: saturate(2);
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale)
- var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-.sepia {
- --tw-sepia: sepia(100%);
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale)
- var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-.filter {
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale)
- var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-.filter-none {
- filter: none;
-}
-.backdrop-blur-lg {
- --tw-backdrop-blur: blur(16px);
- backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
- var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
-}
-.backdrop-brightness-50 {
- --tw-backdrop-brightness: brightness(0.5);
- backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
- var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
-}
-.backdrop-contrast-0 {
- --tw-backdrop-contrast: contrast(0);
- backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
- var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
-}
-.backdrop-grayscale {
- --tw-backdrop-grayscale: grayscale(100%);
- backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
- var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
-}
-.backdrop-hue-rotate-90 {
- --tw-backdrop-hue-rotate: hue-rotate(90deg);
- backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
- var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
-}
-.backdrop-invert {
- --tw-backdrop-invert: invert(100%);
- backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
- var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
-}
-.backdrop-opacity-75 {
- --tw-backdrop-opacity: opacity(0.75);
- backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
- var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
-}
-.backdrop-saturate-150 {
- --tw-backdrop-saturate: saturate(1.5);
- backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
- var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
-}
-.backdrop-sepia {
- --tw-backdrop-sepia: sepia(100%);
- backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
- var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
-}
-.backdrop-filter {
- backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
- var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
-}
-.backdrop-filter-none {
- backdrop-filter: none;
-}
-.transition {
- transition-property: color, background-color, border-color, text-decoration-color, fill, stroke,
- opacity, box-shadow, transform, filter, backdrop-filter;
- transition-duration: 0.15s;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
-}
-.transition-all {
- transition-property: all;
- transition-duration: 0.15s;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
-}
-.delay-300 {
- transition-delay: 0.3s;
-}
-.duration-200 {
- transition-duration: 0.2s;
-}
-.ease-in-out {
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
-}
-.will-change-scroll {
- will-change: scroll-position;
-}
-.will-change-transform {
- will-change: transform;
-}
-.content-none {
- --tw-content: none;
- content: var(--tw-content);
-}
diff --git a/tests/basic-usage.test.css b/tests/basic-usage.test.css
index 720c783e9..24a414b45 100644
--- a/tests/basic-usage.test.css
+++ b/tests/basic-usage.test.css
@@ -1,6 +1,103 @@
*,
:before,
-:after,
+:after {
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --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-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-gradient-from-position: ;
+ --tw-gradient-via-position: ;
+ --tw-gradient-to-position: ;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: #3b82f680;
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+}
+::-webkit-backdrop {
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --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-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-gradient-from-position: ;
+ --tw-gradient-via-position: ;
+ --tw-gradient-to-position: ;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: #3b82f680;
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+}
::backdrop {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
@@ -79,11 +176,11 @@
}
}
.sr-only {
- width: 1px;
- height: 1px;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
+ width: 1px;
+ height: 1px;
margin: -1px;
padding: 0;
position: absolute;
@@ -196,20 +293,65 @@
display: none;
}
.aspect-square {
- aspect-ratio: 1 / 1;
+ aspect-ratio: 1;
}
.aspect-video {
aspect-ratio: 16 / 9;
}
+.h-0 {
+ height: 0;
+}
.h-16 {
height: 4rem;
}
+.h-dvh {
+ height: 100dvh;
+}
+.h-full {
+ height: 100%;
+}
+.h-lvh {
+ height: 100lvh;
+}
+.h-screen {
+ height: 100vh;
+}
+.h-svh {
+ height: 100svh;
+}
+.max-h-0 {
+ max-height: 0;
+}
+.max-h-dvh {
+ max-height: 100dvh;
+}
+.max-h-full {
+ max-height: 100%;
+}
+.max-h-lvh {
+ max-height: 100lvh;
+}
.max-h-screen {
max-height: 100vh;
}
+.max-h-svh {
+ max-height: 100svh;
+}
.min-h-0 {
min-height: 0;
}
+.min-h-dvh {
+ min-height: 100dvh;
+}
+.min-h-full {
+ min-height: 100%;
+}
+.min-h-lvh {
+ min-height: 100lvh;
+}
+.min-h-svh {
+ min-height: 100svh;
+}
.w-12 {
width: 3rem;
}
@@ -382,6 +524,7 @@
touch-action: manipulation;
}
.select-none {
+ -webkit-user-select: none;
user-select: none;
}
.resize-none {
@@ -415,6 +558,7 @@
list-style-image: none;
}
.appearance-none {
+ -webkit-appearance: none;
appearance: none;
}
.columns-1 {
@@ -552,6 +696,15 @@
.whitespace-nowrap {
white-space: nowrap;
}
+.text-wrap {
+ text-wrap: wrap;
+}
+.text-nowrap {
+ text-wrap: nowrap;
+}
+.text-balance {
+ text-wrap: balance;
+}
.break-words {
overflow-wrap: break-word;
}
@@ -966,69 +1119,110 @@
}
.backdrop-blur-lg {
--tw-backdrop-blur: blur(16px);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
+ var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-brightness-50 {
--tw-backdrop-brightness: brightness(0.5);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
+ var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-contrast-0 {
--tw-backdrop-contrast: contrast(0);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
+ var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-grayscale {
--tw-backdrop-grayscale: grayscale(100%);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
+ var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-hue-rotate-90 {
--tw-backdrop-hue-rotate: hue-rotate(90deg);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
+ var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-invert {
--tw-backdrop-invert: invert(100%);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
+ var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-opacity-75 {
--tw-backdrop-opacity: opacity(0.75);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
+ var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-saturate-150 {
--tw-backdrop-saturate: saturate(1.5);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
+ var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-sepia {
--tw-backdrop-sepia: sepia(100%);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
+ var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-filter {
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
+ var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-filter-none {
+ -webkit-backdrop-filter: none;
backdrop-filter: none;
}
.transition {
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke,
- opacity, box-shadow, transform, filter, backdrop-filter;
+ opacity, box-shadow, transform, filter, -webkit-backdrop-filter, backdrop-filter;
transition-duration: 0.15s;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
diff --git a/tests/basic-usage.test.js b/tests/basic-usage.test.js
index 53c2a4418..d32ac43c9 100644
--- a/tests/basic-usage.test.js
+++ b/tests/basic-usage.test.js
@@ -1,133 +1,134 @@
import fs from 'fs'
import path from 'path'
-import { crosscheck, run, html, css, defaults } from './util/run'
+import { run, html, css, defaults } from './util/run'
+import { flagEnabled } from '../src/featureFlags'
-crosscheck(({ stable, oxide }) => {
- test('basic usage', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
{
backdrop-saturate-150
backdrop-sepia
"
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- stable
- .expect(result.css)
- .toMatchFormattedCss(
- fs.readFileSync(path.resolve(__dirname, './basic-usage.test.css'), 'utf8')
- )
- oxide
- .expect(result.css)
- .toMatchFormattedCss(
- fs.readFileSync(path.resolve(__dirname, './basic-usage.oxide.test.css'), 'utf8')
- )
- })
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(
+ fs.readFileSync(path.resolve(__dirname, './basic-usage.test.css'), 'utf8')
+ )
})
+})
- test('all plugins are executed that match a candidate', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- colors: {
- green: {
- light: 'green',
- },
+test('all plugins are executed that match a candidate', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ colors: {
+ green: {
+ light: 'green',
},
},
- corePlugins: { preflight: false },
+ },
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+
+ .bg-green {
+ /* Empty on purpose */
}
+ `
- let input = css`
- @tailwind utilities;
-
- .bg-green {
- /* Empty on purpose */
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-green-light {
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 128 0 / var(--tw-bg-opacity));
}
- `
-
- return run(input, config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .bg-green-light {
- --tw-bg-opacity: 1;
- background-color: rgb(0 128 0 / var(--tw-bg-opacity));
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .bg-green-light {
- background-color: green;
- }
- `)
- })
+ `)
})
+})
- test('per-plugin colors with the same key can differ when using a custom colors object', () => {
- let config = {
- content: [
- {
- raw: html`
-
This should be green text on red background.
- `,
+test('per-plugin colors with the same key can differ when using a custom colors object', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
This should be green text on red background.
+ `,
+ },
+ ],
+ theme: {
+ // colors & theme MUST be plain objects
+ // If they're functions here the test passes regardless
+ colors: {
+ theme: {
+ bg: 'red',
+ text: 'green',
},
- ],
- theme: {
- // colors & theme MUST be plain objects
- // If they're functions here the test passes regardless
- colors: {
+ },
+ extend: {
+ textColor: {
theme: {
- bg: 'red',
- text: 'green',
+ DEFAULT: 'green',
},
},
- extend: {
- textColor: {
- theme: {
- DEFAULT: 'green',
- },
- },
- backgroundColor: {
- theme: {
- DEFAULT: 'red',
- },
+ backgroundColor: {
+ theme: {
+ DEFAULT: 'red',
},
},
},
- corePlugins: { preflight: false },
- }
+ },
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .bg-theme {
- --tw-bg-opacity: 1;
- background-color: rgb(255 0 0 / var(--tw-bg-opacity));
- }
- .text-theme {
- --tw-text-opacity: 1;
- color: rgb(0 128 0 / var(--tw-text-opacity));
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .bg-theme {
- background-color: red;
- }
- .text-theme {
- color: green;
- }
- `)
- })
- })
-
- test('default ring color can be a function', () => {
- function color(variable) {
- return function ({ opacityVariable, opacityValue }) {
- if (opacityValue !== undefined) {
- return `rgba(${variable}, ${opacityValue})`
- }
- if (opacityVariable !== undefined) {
- return `rgba(${variable}, var(${opacityVariable}, 1))`
- }
- return `rgb(${variable})`
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-theme {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 0 0 / var(--tw-bg-opacity));
}
+ .text-theme {
+ --tw-text-opacity: 1;
+ color: rgb(0 128 0 / var(--tw-text-opacity));
+ }
+ `)
+ })
+})
+
+test('default ring color can be a function', () => {
+ function color(variable) {
+ return function ({ opacityVariable, opacityValue }) {
+ if (opacityValue !== undefined) {
+ return `rgba(${variable}, ${opacityValue})`
+ }
+ if (opacityVariable !== undefined) {
+ return `rgba(${variable}, var(${opacityVariable}, 1))`
+ }
+ return `rgb(${variable})`
}
+ }
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
- theme: {
- extend: {
- ringColor: {
- DEFAULT: color('var(--red)'),
- },
+ theme: {
+ extend: {
+ ringColor: {
+ DEFAULT: color('var(--red)'),
},
},
- plugins: [],
- corePlugins: { preflight: false },
- }
+ },
+ plugins: [],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults({ defaultRingColor: 'rgba(var(--red), 0.5)' })}
- .ring {
- --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(3px + 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);
- }
- `)
- })
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults({ defaultRingColor: 'rgba(var(--red), 0.5)' })}
+ .ring {
+ --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(3px + 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);
+ }
+ `)
})
+})
- it('falsy config values still work', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- inset: {
- 0: 0,
- },
+it('falsy config values still work', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ inset: {
+ 0: 0,
},
- plugins: [],
- corePlugins: { preflight: false },
- }
+ },
+ plugins: [],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .inset-0 {
- inset: 0;
- }
- `)
- })
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .inset-0 {
+ inset: 0;
+ }
+ `)
})
+})
- it('shadows support values without a leading zero', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- boxShadow: {
- one: '0.5rem 0.5rem 0.5rem #0005',
- two: '.5rem .5rem .5rem #0005',
- },
+it('shadows support values without a leading zero', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ boxShadow: {
+ one: '0.5rem 0.5rem 0.5rem #0005',
+ two: '.5rem .5rem .5rem #0005',
},
- plugins: [],
- corePlugins: { preflight: false },
- }
+ },
+ plugins: [],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .shadow-one,
- .shadow-two {
- --tw-shadow: 0.5rem 0.5rem 0.5rem #0005;
- --tw-shadow-colored: 0.5rem 0.5rem 0.5rem var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
- `)
- })
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .shadow-one,
+ .shadow-two {
+ --tw-shadow: 0.5rem 0.5rem 0.5rem #0005;
+ --tw-shadow-colored: 0.5rem 0.5rem 0.5rem var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
+ var(--tw-shadow);
+ }
+ `)
})
+})
- it('can scan extremely long classes without crashing', () => {
- let val = 'cols-' + '-a'.repeat(65536)
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
+it('can scan extremely long classes without crashing', () => {
+ let val = 'cols-' + '-a'.repeat(65536)
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css``)
- })
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css``)
})
+})
- it('does not produce duplicate output when seeing variants preceding a wildcard (*)', () => {
- let config = {
- content: [{ raw: html`underline focus:*` }],
- corePlugins: { preflight: false },
+it('does not produce duplicate output when seeing variants preceding a wildcard (*)', () => {
+ let config = {
+ content: [{ raw: html`underline focus:*` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+
+ * {
+ color: red;
}
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
+ .combined,
+ * {
+ text-align: center;
+ }
+ @layer base {
* {
- color: red;
+ color: blue;
}
+ .combined,
+ * {
+ color: red;
+ }
+ }
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .combined,
+ * {
+ color: red;
+ }
+ ${defaults}
+ .underline {
+ text-decoration-line: underline;
+ }
+ * {
+ color: red;
+ }
.combined,
* {
text-align: center;
}
+ `)
+ })
+})
- @layer base {
- * {
- color: blue;
- }
+it('can parse box shadows with variables', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ boxShadow: {
+ lg: 'var(--a, 0 35px 60px -15px rgba(0, 0, 0)), 0 0 1px rgb(0, 0, 0)',
+ },
+ },
+ corePlugins: { preflight: false },
+ }
- .combined,
- * {
- color: red;
- }
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .shadow-lg {
+ --tw-shadow: var(--a, 0 35px 60px -15px #000), 0 0 1px #000;
+ --tw-shadow-colored: 0 35px 60px -15px var(--tw-shadow-color),
+ 0 0 1px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
+ var(--tw-shadow);
}
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- * {
- color: #00f;
- }
- .combined,
- * {
- color: red;
- }
- ${defaults}
- .underline {
- text-decoration-line: underline;
- }
- * {
- color: red;
- }
- .combined,
- * {
- text-align: center;
- }
- `)
- })
+ `)
})
+})
- it('can parse box shadows with variables', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- boxShadow: {
- lg: 'var(--a, 0 35px 60px -15px rgba(0, 0, 0)), 0 0 1px rgb(0, 0, 0)',
- },
- },
- corePlugins: { preflight: false },
- }
+it('should generate styles using :not(.unknown-class) even if `.unknown-class` does not exist', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind components;
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .shadow-lg {
- --tw-shadow: var(--a, 0 35px 60px -15px #000), 0 0 1px #000;
- --tw-shadow-colored: 0 35px 60px -15px var(--tw-shadow-color),
- 0 0 1px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
- `)
- })
- })
-
- it('should generate styles using :not(.unknown-class) even if `.unknown-class` does not exist', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind components;
-
- @layer components {
- div:not(.unknown-class) {
- color: red;
- }
+ @layer components {
+ div:not(.unknown-class) {
+ color: red;
}
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- div:not(.unknown-class) {
- color: red;
- }
- `)
- })
- })
-
- it('supports multiple backgrounds as arbitrary values even if only some are quoted', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: { preflight: false },
}
+ `
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .bg-\[url\(\'\/images\/one-two-three\.png\'\)\,linear-gradient\(to_right\,_\#eeeeee\,_\#000000\)\] {
- background-image: url('/images/one-two-three.png'), linear-gradient(to right, #eee, #000);
- }
- `)
- })
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ div:not(.unknown-class) {
+ color: red;
+ }
+ `)
})
+})
- it('The "default" ring opacity is used by the default ring color when not using respectDefaultRingColorOpacity (1)', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults({ defaultRingColor: '#3b82f680' })}
- .ring {
- --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(3px + 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);
- }
- `)
- })
- })
-
- it('The "default" ring opacity is used by the default ring color when not using respectDefaultRingColorOpacity (2)', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- theme: {
- ringOpacity: {
- DEFAULT: 0.75,
- },
+it('supports multiple backgrounds as arbitrary values even if only some are quoted', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
},
- }
+ ],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults({ defaultRingColor: '#3b82f6bf' })}
- .ring {
- --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(3px + 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);
- }
- `)
- })
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-\[url\(\'\/images\/one-two-three\.png\'\)\,linear-gradient\(to_right\,_\#eeeeee\,_\#000000\)\] {
+ background-image: url('/images/one-two-three.png'), linear-gradient(to right, #eee, #000);
+ }
+ `)
})
+})
- it('Customizing the default ring color uses the "default" opacity when not using respectDefaultRingColorOpacity (1)', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- theme: {
- ringColor: {
- DEFAULT: '#ff7f7f',
- },
+it('The "default" ring opacity is used by the default ring color when not using respectDefaultRingColorOpacity (1)', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults({ defaultRingColor: '#3b82f680' })}
+ .ring {
+ --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(3px + 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);
+ }
+ `)
+ })
+})
+
+it('The "default" ring opacity is used by the default ring color when not using respectDefaultRingColorOpacity (2)', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ theme: {
+ ringOpacity: {
+ DEFAULT: 0.75,
},
- }
+ },
+ }
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults({ defaultRingColor: '#ff7f7f80' })}
- .ring {
- --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(3px + 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);
- }
- `)
- })
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults({ defaultRingColor: '#3b82f6bf' })}
+ .ring {
+ --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(3px + 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);
+ }
+ `)
})
+})
- it('Customizing the default ring color uses the "default" opacity when not using respectDefaultRingColorOpacity (2)', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- theme: {
- ringColor: {
- DEFAULT: '#ff7f7f00',
- },
+it('Customizing the default ring color uses the "default" opacity when not using respectDefaultRingColorOpacity (1)', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ theme: {
+ ringColor: {
+ DEFAULT: '#ff7f7f',
},
- }
+ },
+ }
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults({ defaultRingColor: '#ff7f7f80' })}
- .ring {
- --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(3px + 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);
- }
- `)
- })
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults({ defaultRingColor: '#ff7f7f80' })}
+ .ring {
+ --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(3px + 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);
+ }
+ `)
})
+})
- it('The "default" ring color ignores the default opacity when using respectDefaultRingColorOpacity (1)', () => {
- let config = {
- future: { respectDefaultRingColorOpacity: true },
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults({ defaultRingColor: '#3b82f67f' })}
- .ring {
- --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(3px + 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);
- }
- `)
- })
- })
-
- it('The "default" ring color ignores the default opacity when using respectDefaultRingColorOpacity (2)', () => {
- let config = {
- future: { respectDefaultRingColorOpacity: true },
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- theme: {
- ringOpacity: {
- DEFAULT: 0.75,
- },
+it('Customizing the default ring color uses the "default" opacity when not using respectDefaultRingColorOpacity (2)', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ theme: {
+ ringColor: {
+ DEFAULT: '#ff7f7f00',
},
- }
+ },
+ }
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults({ defaultRingColor: '#3b82f67f' })}
- .ring {
- --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(3px + 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);
- }
- `)
- })
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults({ defaultRingColor: '#ff7f7f80' })}
+ .ring {
+ --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(3px + 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);
+ }
+ `)
})
+})
- it('Customizing the default ring color preserves its opacity when using respectDefaultRingColorOpacity (1)', () => {
- let config = {
- future: { respectDefaultRingColorOpacity: true },
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- theme: {
- ringColor: {
- DEFAULT: '#ff7f7f',
- },
- },
- }
+it('The "default" ring color ignores the default opacity when using respectDefaultRingColorOpacity (1)', () => {
+ let config = {
+ future: { respectDefaultRingColorOpacity: true },
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults({ defaultRingColor: '#ff7f7f' })}
- .ring {
- --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(3px + 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);
- }
- `)
- })
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults({ defaultRingColor: '#3b82f67f' })}
+ .ring {
+ --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(3px + 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);
+ }
+ `)
})
+})
- it('Customizing the default ring color preserves its opacity when using respectDefaultRingColorOpacity (2)', () => {
- let config = {
- future: { respectDefaultRingColorOpacity: true },
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- theme: {
- ringColor: {
- DEFAULT: '#ff7f7f00',
- },
+it('The "default" ring color ignores the default opacity when using respectDefaultRingColorOpacity (2)', () => {
+ let config = {
+ future: { respectDefaultRingColorOpacity: true },
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ theme: {
+ ringOpacity: {
+ DEFAULT: 0.75,
},
- }
+ },
+ }
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults({ defaultRingColor: '#ff7f7f00' })}
- .ring {
- --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(3px + 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);
- }
- `)
- })
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults({ defaultRingColor: '#3b82f67f' })}
+ .ring {
+ --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(3px + 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);
+ }
+ `)
})
+})
- it('A bare ring-opacity utility is not supported when not using respectDefaultRingColorOpacity', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- theme: {
- ringOpacity: {
- DEFAULT: '0.33',
- },
+it('Customizing the default ring color preserves its opacity when using respectDefaultRingColorOpacity (1)', () => {
+ let config = {
+ future: { respectDefaultRingColorOpacity: true },
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ theme: {
+ ringColor: {
+ DEFAULT: '#ff7f7f',
},
- }
+ },
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults({ defaultRingColor: '#ff7f7f' })}
+ .ring {
+ --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(3px + 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);
+ }
+ `)
+ })
+})
+
+it('Customizing the default ring color preserves its opacity when using respectDefaultRingColorOpacity (2)', () => {
+ let config = {
+ future: { respectDefaultRingColorOpacity: true },
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ theme: {
+ ringColor: {
+ DEFAULT: '#ff7f7f00',
+ },
+ },
+ }
+
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults({ defaultRingColor: '#ff7f7f00' })}
+ .ring {
+ --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(3px + 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);
+ }
+ `)
+ })
+})
+
+it('A bare ring-opacity utility is not supported when not using respectDefaultRingColorOpacity', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ theme: {
+ ringOpacity: {
+ DEFAULT: '0.33',
+ },
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css``)
+ })
+})
+
+test('A bare ring-opacity utility is supported when using respectDefaultRingColorOpacity', () => {
+ let config = {
+ future: { respectDefaultRingColorOpacity: true },
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ theme: {
+ ringOpacity: {
+ DEFAULT: '0.33',
+ },
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .ring-opacity {
+ --tw-ring-opacity: 0.33;
+ }
+ `)
+ })
+})
+
+it('Ring color utilities are generated when using respectDefaultRingColorOpacity', () => {
+ let config = {
+ future: { respectDefaultRingColorOpacity: true },
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .ring {
+ --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(3px + 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-blue-500 {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity));
+ }
+ `)
+ })
+})
+
+test('should not crash when group names contain special characters', () => {
+ let config = {
+ future: { respectDefaultRingColorOpacity: true },
+ content: [
+ {
+ raw: '
',
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ if (flagEnabled(config, 'oxideParser')) {
expect(result.css).toMatchFormattedCss(css``)
- })
- })
-
- test('A bare ring-opacity utility is supported when using respectDefaultRingColorOpacity', () => {
- let config = {
- future: { respectDefaultRingColorOpacity: true },
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- theme: {
- ringOpacity: {
- DEFAULT: '0.33',
- },
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .ring-opacity {
- --tw-ring-opacity: 0.33;
- }
- `)
- // The opacity plugins are disabled by default in the `oxide` engine
- oxide.expect(result.css).toMatchFormattedCss(css``)
- })
- })
-
- it('Ring color utilities are generated when using respectDefaultRingColorOpacity', () => {
- let config = {
- future: { respectDefaultRingColorOpacity: true },
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .ring {
- --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(3px + 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-blue-500 {
- --tw-ring-opacity: 1;
- --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity));
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .ring {
- --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(3px + 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-blue-500 {
- --tw-ring-color: #3b82f6;
- }
- `)
- })
- })
-
- oxide.test.todo('should not crash when group names contain special characters')
- stable.test('should not crash when group names contain special characters', () => {
- let config = {
- future: { respectDefaultRingColorOpacity: true },
- content: [
- {
- raw: '
',
- },
- ],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
+ } else {
expect(result.css).toMatchFormattedCss(css`
.group\/\$\{id\}:hover .group-hover\/\$\{id\}\:visible {
visibility: visible;
}
`)
- })
- })
-
- it('should not crash when matching variants where utility classes are doubled up', () => {
- let config = {
- content: [
- {
- raw: '
',
- },
- ],
}
-
- let input = css`
- @tailwind utilities;
- @layer utilities {
- .foo.foo {
- text-decoration-line: underline;
- }
- }
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .hover\:foo:hover.hover\:foo:hover {
- text-decoration-line: underline;
- }
- `)
- })
})
+})
- test('detects quoted arbitrary values containing a slash', async () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
+it('should not crash when matching variants where utility classes are doubled up', () => {
+ let config = {
+ content: [
+ {
+ raw: '
',
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ @layer utilities {
+ .foo.foo {
+ text-decoration-line: underline;
+ }
}
+ `
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- oxide.expect(result.css).toMatchFormattedCss(css`
- .group[href^='/'] .group-\[\[href\^\=\'\/\'\]\]\:hidden {
- display: none;
- }
- `)
-
- stable.expect(result.css).toMatchFormattedCss(css`
- .hidden,
- .group[href^='/'] .group-\[\[href\^\=\'\/\'\]\]\:hidden {
- display: none;
- }
- `)
- })
-
- test('handled quoted arbitrary values containing escaped spaces', async () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- oxide.expect(result.css).toMatchFormattedCss(css`
- .group[href^=' bar'] .group-\[\[href\^\=\'_bar\'\]\]\:hidden {
- display: none;
- }
- `)
-
- stable.expect(result.css).toMatchFormattedCss(css`
- .hidden,
- .group[href^=' bar'] .group-\[\[href\^\=\'_bar\'\]\]\:hidden {
- display: none;
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .hover\:foo:hover.hover\:foo:hover {
+ text-decoration-line: underline;
}
`)
})
})
+
+test('detects quoted arbitrary values containing a slash', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(
+ flagEnabled(config, 'oxideParser')
+ ? css`
+ .group[href^='/'] .group-\[\[href\^\=\'\/\'\]\]\:hidden {
+ display: none;
+ }
+ `
+ : css`
+ .hidden,
+ .group[href^='/'] .group-\[\[href\^\=\'\/\'\]\]\:hidden {
+ display: none;
+ }
+ `
+ )
+})
+
+test('handled quoted arbitrary values containing escaped spaces', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(
+ flagEnabled(config, 'oxideParser')
+ ? css`
+ .group[href^=' bar'] .group-\[\[href\^\=\'_bar\'\]\]\:hidden {
+ display: none;
+ }
+ `
+ : css`
+ .hidden,
+ .group[href^=' bar'] .group-\[\[href\^\=\'_bar\'\]\]\:hidden {
+ display: none;
+ }
+ `
+ )
+})
diff --git a/tests/blocklist.test.js b/tests/blocklist.test.js
index 3642aa376..e9915350f 100644
--- a/tests/blocklist.test.js
+++ b/tests/blocklist.test.js
@@ -1,126 +1,107 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(({ stable, oxide }) => {
- it('can block classes matched literally', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- blocklist: ['font', 'uppercase', 'hover:text-sm', 'bg-red-500/50', 'my-custom-class'],
+it('can block classes matched literally', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ blocklist: ['font', 'uppercase', 'hover:text-sm', 'bg-red-500/50', 'my-custom-class'],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ .my-custom-class {
+ color: red;
}
+ `
- let input = css`
- @tailwind utilities;
+ return run(input, config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
.my-custom-class {
color: red;
}
- `
-
- return run(input, config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
+ @media (min-width: 640px) {
+ .sm\:hover\:text-sm:hover {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
}
- .my-custom-class {
- color: red;
- }
- @media (min-width: 640px) {
- .sm\:hover\:text-sm:hover {
- font-size: 0.875rem;
- line-height: 1.25rem;
- }
- }
- `)
- })
- })
-
- it('can block classes inside @layer', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- blocklist: ['my-custom-class'],
- }
-
- let input = css`
- @tailwind utilities;
- @layer utilities {
- .my-custom-class {
- color: red;
- }
- }
- `
-
- return run(input, config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- `)
- })
- })
-
- it('blocklists do NOT support regexes', async () => {
- let config = {
- content: [{ raw: html`
` }],
- blocklist: [/^bg-\[[^]+\]$/],
- }
-
- let result = await run('@tailwind utilities', config)
-
- stable.expect(result.css).toMatchFormattedCss(css`
- .bg-\[\#f00d1e\] {
- --tw-bg-opacity: 1;
- background-color: rgb(240 13 30 / var(--tw-bg-opacity));
- }
- .font-bold {
- font-weight: 700;
}
`)
-
- oxide.expect(result.css).toMatchFormattedCss(css`
- .bg-\[\#f00d1e\] {
- background-color: #f00d1e;
- }
- .font-bold {
- font-weight: 700;
- }
- `)
-
- expect().toHaveBeenWarnedWith(['blocklist-invalid'])
- })
-
- it('can block classes generated by the safelist', () => {
- let config = {
- content: [{ raw: html`
` }],
- safelist: [{ pattern: /^bg-red-(400|500)$/ }],
- blocklist: ['bg-red-500'],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .bg-red-400 {
- --tw-bg-opacity: 1;
- background-color: rgb(248 113 113 / var(--tw-bg-opacity));
- }
- .font-bold {
- font-weight: 700;
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .bg-red-400 {
- background-color: #f87171;
- }
- .font-bold {
- font-weight: 700;
- }
- `)
- })
+ })
+})
+
+it('can block classes inside @layer', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ blocklist: ['my-custom-class'],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ @layer utilities {
+ .my-custom-class {
+ color: red;
+ }
+ }
+ `
+
+ return run(input, config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ `)
+ })
+})
+
+it('blocklists do NOT support regexes', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ blocklist: [/^bg-\[[^]+\]$/],
+ }
+
+ let result = await run('@tailwind utilities', config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-\[\#f00d1e\] {
+ --tw-bg-opacity: 1;
+ background-color: rgb(240 13 30 / var(--tw-bg-opacity));
+ }
+ .font-bold {
+ font-weight: 700;
+ }
+ `)
+
+ expect().toHaveBeenWarnedWith(['blocklist-invalid'])
+})
+
+it('can block classes generated by the safelist', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ safelist: [{ pattern: /^bg-red-(400|500)$/ }],
+ blocklist: ['bg-red-500'],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-red-400 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(248 113 113 / var(--tw-bg-opacity));
+ }
+ .font-bold {
+ font-weight: 700;
+ }
+ `)
})
})
diff --git a/tests/collapse-adjacent-rules.test.js b/tests/collapse-adjacent-rules.test.js
index 4dbaab769..69c471686 100644
--- a/tests/collapse-adjacent-rules.test.js
+++ b/tests/collapse-adjacent-rules.test.js
@@ -1,114 +1,119 @@
-import { crosscheck, run, html, css, defaults } from './util/run'
+import { run, html, css, defaults } from './util/run'
-crosscheck(({ stable, oxide }) => {
- test('collapse adjacent rules', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {},
- plugins: [
- function ({ addVariant }) {
- addVariant('foo-bar', '@supports (foo: bar)')
- },
- ],
+test('collapse adjacent rules', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {},
+ plugins: [
+ function ({ addVariant }) {
+ addVariant('foo-bar', '@supports (foo: bar)')
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind base;
+ @font-face {
+ font-family: 'Inter';
+ src: url('/fonts/Inter.woff2') format('woff2'), url('/fonts/Inter.woff') format('woff');
}
-
- let input = css`
- @tailwind base;
+ @font-face {
+ font-family: 'Gilroy';
+ src: url('/fonts/Gilroy.woff2') format('woff2'), url('/fonts/Gilroy.woff') format('woff');
+ }
+ @page {
+ margin: 1cm;
+ }
+ @tailwind components;
+ @tailwind utilities;
+ @layer base {
@font-face {
- font-family: 'Inter';
+ font-family: 'Poppins';
+ src: url('/fonts/Poppins.woff2') format('woff2'), url('/fonts/Poppins.woff') format('woff');
+ }
+ @font-face {
+ font-family: 'Proxima Nova';
+ src: url('/fonts/ProximaNova.woff2') format('woff2'),
+ url('/fonts/ProximaNova.woff') format('woff');
+ }
+ }
+ .foo,
+ .bar {
+ color: black;
+ }
+ .foo,
+ .bar {
+ font-weight: 700;
+ }
+ .some-apply-thing {
+ @apply foo-bar:md:text-black foo-bar:md:font-bold foo-bar:text-black foo-bar:font-bold md:font-bold md:text-black;
+ }
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @font-face {
+ font-family: Poppins;
+ src: url('/fonts/Poppins.woff2') format('woff2'), url('/fonts/Poppins.woff') format('woff');
+ }
+ @font-face {
+ font-family: Proxima Nova;
+ src: url('/fonts/ProximaNova.woff2') format('woff2'),
+ url('/fonts/ProximaNova.woff') format('woff');
+ }
+ ${defaults}
+ @font-face {
+ font-family: Inter;
src: url('/fonts/Inter.woff2') format('woff2'), url('/fonts/Inter.woff') format('woff');
}
@font-face {
- font-family: 'Gilroy';
+ font-family: Gilroy;
src: url('/fonts/Gilroy.woff2') format('woff2'), url('/fonts/Gilroy.woff') format('woff');
}
@page {
margin: 1cm;
}
- @tailwind components;
- @tailwind utilities;
- @layer base {
- @font-face {
- font-family: 'Poppins';
- src: url('/fonts/Poppins.woff2') format('woff2'),
- url('/fonts/Poppins.woff') format('woff');
- }
- @font-face {
- font-family: 'Proxima Nova';
- src: url('/fonts/ProximaNova.woff2') format('woff2'),
- url('/fonts/ProximaNova.woff') format('woff');
- }
- }
- .foo,
- .bar {
- color: black;
- }
- .foo,
- .bar {
+ .font-bold {
font-weight: 700;
}
- .some-apply-thing {
- @apply foo-bar:md:text-black foo-bar:md:font-bold foo-bar:text-black foo-bar:font-bold md:font-bold md:text-black;
+ .foo,
+ .bar {
+ color: #000;
+ font-weight: 700;
}
- `
-
- return run(input, config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- @font-face {
- font-family: Poppins;
- src: url('/fonts/Poppins.woff2') format('woff2'),
- url('/fonts/Poppins.woff') format('woff');
- }
- @font-face {
- font-family: Proxima Nova;
- src: url('/fonts/ProximaNova.woff2') format('woff2'),
- url('/fonts/ProximaNova.woff') format('woff');
- }
- ${defaults}
- @font-face {
- font-family: Inter;
- src: url('/fonts/Inter.woff2') format('woff2'), url('/fonts/Inter.woff') format('woff');
- }
- @font-face {
- font-family: Gilroy;
- src: url('/fonts/Gilroy.woff2') format('woff2'), url('/fonts/Gilroy.woff') format('woff');
- }
- @page {
- margin: 1cm;
- }
- .font-bold {
+ @supports (foo: bar) {
+ .some-apply-thing {
+ --tw-text-opacity: 1;
+ color: rgb(0 0 0 / var(--tw-text-opacity));
font-weight: 700;
}
- .foo,
- .bar {
- color: #000;
+ }
+ @media (min-width: 768px) {
+ .some-apply-thing {
+ --tw-text-opacity: 1;
+ color: rgb(0 0 0 / var(--tw-text-opacity));
font-weight: 700;
}
- @supports (foo: bar) {
- .some-apply-thing {
- --tw-text-opacity: 1;
- color: rgb(0 0 0 / var(--tw-text-opacity));
- font-weight: 700;
- }
- }
+ }
+ @supports (foo: bar) {
@media (min-width: 768px) {
.some-apply-thing {
--tw-text-opacity: 1;
@@ -116,134 +121,49 @@ crosscheck(({ stable, oxide }) => {
font-weight: 700;
}
}
- @supports (foo: bar) {
- @media (min-width: 768px) {
- .some-apply-thing {
- --tw-text-opacity: 1;
- color: rgb(0 0 0 / var(--tw-text-opacity));
- font-weight: 700;
- }
- }
+ }
+ @media (min-width: 640px) {
+ .sm\:text-center {
+ text-align: center;
}
- @media (min-width: 640px) {
- .sm\:text-center {
- text-align: center;
- }
- .sm\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 768px) {
- .md\:text-center {
- text-align: center;
- }
- .md\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 1024px) {
- .lg\:text-center {
- text-align: center;
- }
- .lg\:font-bold {
- font-weight: 700;
- }
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- @font-face {
- font-family: Poppins;
- src: url('/fonts/Poppins.woff2') format('woff2'),
- url('/fonts/Poppins.woff') format('woff');
- }
- @font-face {
- font-family: Proxima Nova;
- src: url('/fonts/ProximaNova.woff2') format('woff2'),
- url('/fonts/ProximaNova.woff') format('woff');
- }
- ${defaults}
- @font-face {
- font-family: Inter;
- src: url('/fonts/Inter.woff2') format('woff2'), url('/fonts/Inter.woff') format('woff');
- }
- @font-face {
- font-family: Gilroy;
- src: url('/fonts/Gilroy.woff2') format('woff2'), url('/fonts/Gilroy.woff') format('woff');
- }
- @page {
- margin: 1cm;
- }
- .font-bold {
+ .sm\:font-bold {
font-weight: 700;
}
- .foo,
- .bar {
- color: #000;
+ }
+ @media (min-width: 768px) {
+ .md\:text-center {
+ text-align: center;
+ }
+ .md\:font-bold {
font-weight: 700;
}
- @supports (foo: bar) {
- .some-apply-thing {
- color: #000;
- font-weight: 700;
- }
+ }
+ @media (min-width: 1024px) {
+ .lg\:text-center {
+ text-align: center;
}
- @media (min-width: 768px) {
- .some-apply-thing {
- color: #000;
- font-weight: 700;
- }
+ .lg\:font-bold {
+ font-weight: 700;
}
- @supports (foo: bar) {
- @media (min-width: 768px) {
- .some-apply-thing {
- color: #000;
- font-weight: 700;
- }
- }
- }
- @media (min-width: 640px) {
- .sm\:text-center {
- text-align: center;
- }
- .sm\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 768px) {
- .md\:text-center {
- text-align: center;
- }
- .md\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 1024px) {
- .lg\:text-center {
- text-align: center;
- }
- .lg\:font-bold {
- font-weight: 700;
- }
- }
- `)
- })
- })
-
- test('duplicate url imports does not break rule collapsing', () => {
- let config = {
- content: [{ raw: html`` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @import url('https://example.com');
- @import url('https://example.com');
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @import 'https://example.com';
- `)
- })
+ }
+ `)
+ })
+})
+
+test('duplicate url imports does not break rule collapsing', () => {
+ let config = {
+ content: [{ raw: html`` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @import url('https://example.com');
+ @import url('https://example.com');
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @import 'https://example.com';
+ `)
})
})
diff --git a/tests/collapse-duplicate-declarations.test.js b/tests/collapse-duplicate-declarations.test.js
index 128eb9bef..d36d2e83a 100644
--- a/tests/collapse-duplicate-declarations.test.js
+++ b/tests/collapse-duplicate-declarations.test.js
@@ -1,177 +1,171 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(() => {
- it('should collapse duplicate declarations with the same units (px)', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- plugins: [],
- }
+it('should collapse duplicate declarations with the same units (px)', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ plugins: [],
+ }
- let input = css`
- @tailwind utilities;
+ let input = css`
+ @tailwind utilities;
- @layer utilities {
- .example {
- height: 100px;
- height: 200px;
- }
+ @layer utilities {
+ .example {
+ height: 100px;
+ height: 200px;
}
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .example {
- height: 200px;
- }
- `)
- })
- })
-
- it('should collapse duplicate declarations with the same units (no unit)', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- plugins: [],
}
+ `
- let input = css`
- @tailwind utilities;
-
- @layer utilities {
- .example {
- line-height: 3;
- line-height: 2;
- }
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .example {
+ height: 200px;
}
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .example {
- line-height: 2;
- }
- `)
- })
- })
-
- it('should not collapse duplicate declarations with the different units', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- plugins: [],
- }
-
- let input = css`
- @tailwind utilities;
-
- @layer utilities {
- .example {
- height: 100px;
- height: 50%;
- }
- }
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .example {
- height: 100px;
- height: 50%;
- }
- `)
- })
- })
-
- it('should collapse the duplicate declarations with the same unit, but leave the ones with different units', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- plugins: [],
- }
-
- let input = css`
- @tailwind utilities;
-
- @layer utilities {
- .example {
- height: 100px;
- height: 50%;
- height: 20vh;
- height: 200px;
- height: 100%;
- height: 30vh;
- }
- }
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .example {
- height: 200px;
- height: 100%;
- height: 30vh;
- }
- `)
- })
- })
-
- it('should collapse the duplicate declarations with the exact same value', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- plugins: [],
- }
-
- let input = css`
- @tailwind utilities;
-
- @layer utilities {
- .example {
- height: var(--value);
- color: blue;
- height: var(--value);
- }
- }
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .example {
- color: #00f;
- height: var(--value);
- }
- `)
- })
- })
-
- it('should work on a real world example', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- plugins: [],
- }
-
- let input = css`
- @tailwind utilities;
-
- @layer utilities {
- .h-available {
- height: 100%;
- height: 100vh;
- height: -webkit-fill-available;
- }
- }
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .h-available {
- height: 100%;
- height: 100vh;
- height: -webkit-fill-available;
- }
- `)
- })
+ `)
+ })
+})
+
+it('should collapse duplicate declarations with the same units (no unit)', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ plugins: [],
+ }
+
+ let input = css`
+ @tailwind utilities;
+
+ @layer utilities {
+ .example {
+ line-height: 3;
+ line-height: 2;
+ }
+ }
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .example {
+ line-height: 2;
+ }
+ `)
+ })
+})
+
+it('should not collapse duplicate declarations with the different units', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ plugins: [],
+ }
+
+ let input = css`
+ @tailwind utilities;
+
+ @layer utilities {
+ .example {
+ height: 100px;
+ height: 50%;
+ }
+ }
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .example {
+ height: 50%;
+ }
+ `)
+ })
+})
+
+it('should collapse the duplicate declarations with the same unit, but leave the ones with different units', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ plugins: [],
+ }
+
+ let input = css`
+ @tailwind utilities;
+
+ @layer utilities {
+ .example {
+ height: 100px;
+ height: 50%;
+ height: 20vh;
+ height: 200px;
+ height: 100%;
+ height: 30vh;
+ }
+ }
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .example {
+ height: 30vh;
+ }
+ `)
+ })
+})
+
+it('should collapse the duplicate declarations with the exact same value', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ plugins: [],
+ }
+
+ let input = css`
+ @tailwind utilities;
+
+ @layer utilities {
+ .example {
+ height: var(--value);
+ color: blue;
+ height: var(--value);
+ }
+ }
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .example {
+ color: #00f;
+ height: var(--value);
+ }
+ `)
+ })
+})
+
+it('should work on a real world example', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ plugins: [],
+ }
+
+ let input = css`
+ @tailwind utilities;
+
+ @layer utilities {
+ .h-available {
+ height: 100%;
+ height: 100vh;
+ height: -webkit-fill-available;
+ }
+ }
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .h-available {
+ height: 100vh;
+ height: -webkit-fill-available;
+ }
+ `)
})
})
diff --git a/tests/color-opacity-modifiers.test.js b/tests/color-opacity-modifiers.test.js
index 8da4d38c2..a4600a495 100644
--- a/tests/color-opacity-modifiers.test.js
+++ b/tests/color-opacity-modifiers.test.js
@@ -1,264 +1,234 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(({ stable, oxide }) => {
- test('basic color opacity modifier', async () => {
- let config = {
- content: [{ raw: html`
` }],
- }
+test('basic color opacity modifier', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .bg-red-500\/50 {
- background-color: #ef444480;
- }
- `)
- })
- })
-
- test('colors with slashes are matched first', async () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- extend: {
- colors: {
- 'red-500/50': '#ff0000',
- },
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .bg-red-500\/50 {
- --tw-bg-opacity: 1;
- background-color: rgb(255 0 0 / var(--tw-bg-opacity));
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .bg-red-500\/50 {
- background-color: red;
- }
- `)
- })
- })
-
- test('arbitrary color opacity modifier', async () => {
- let config = {
- content: [{ raw: html`
` }],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .bg-red-500\/\[var\(--opacity\)\] {
- background-color: rgb(239 68 68 / var(--opacity));
- }
- `)
- })
- })
-
- test('missing alpha generates nothing', async () => {
- let config = {
- content: [{ raw: html`
` }],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(``)
- })
- })
-
- test('arbitrary color with opacity from scale', async () => {
- let config = {
- content: [{ raw: 'bg-[wheat]/50' }],
- theme: {},
- plugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .bg-\[wheat\]\/50 {
- background-color: #f5deb380;
- }
- `)
- })
- })
-
- test('arbitrary color with arbitrary opacity', async () => {
- let config = {
- content: [{ raw: 'bg-[#bada55]/[0.2]' }],
- theme: {},
- plugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .bg-\[\#bada55\]\/\[0\.2\] {
- background-color: #bada5533;
- }
- `)
- })
- })
-
- test('undefined theme color with opacity from scale', async () => {
- let config = {
- content: [{ raw: 'bg-garbage/50' }],
- theme: {},
- plugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(``)
- })
- })
-
- test('values not in the opacity config are ignored', async () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- opacity: {
- 0: '0',
- 25: '0.25',
- 5: '0.5',
- 75: '0.75',
- 100: '1',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(``)
- })
- })
-
- test('function colors are supported', async () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- colors: {
- blue: ({ opacityValue }) => {
- return `rgba(var(--colors-blue), ${opacityValue})`
- },
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .bg-blue\/50 {
- background-color: rgba(var(--colors-blue), 0.5);
- }
- `)
- })
- })
-
- test('utilities that support any type are supported', async () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
- `,
- },
- ],
- theme: {
- extend: {
- fill: (theme) => theme('colors'),
- },
- },
- plugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .from-red-500\/50 {
- --tw-gradient-from: #ef444480 var(--tw-gradient-from-position);
- --tw-gradient-to: #ef444400 var(--tw-gradient-to-position);
- --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
- }
- .fill-red-500\/25 {
- fill: #ef444440;
- }
- .placeholder-red-500\/75::placeholder {
- color: #ef4444bf;
- }
- `)
- })
- })
-
- test('opacity modifier in combination with partial custom properties', async () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .bg-\[hsl\(123\,50\%\,var\(--foo\)\)\] {
- --tw-bg-opacity: 1;
- background-color: hsl(123 50% var(--foo) / var(--tw-bg-opacity));
- }
- .bg-\[hsl\(123\,50\%\,var\(--foo\)\)\]\/50 {
- background-color: hsl(123 50% var(--foo) / 0.5);
- }
- .bg-\[hsl\(123\,var\(--foo\)\,50\%\)\] {
- --tw-bg-opacity: 1;
- background-color: hsl(123 var(--foo) 50% / var(--tw-bg-opacity));
- }
- .bg-\[hsl\(123\,var\(--foo\)\,50\%\)\]\/50 {
- background-color: hsl(123 var(--foo) 50% / 0.5);
- }
- .bg-\[hsl\(var\(--foo\)\,50\%\,50\%\)\] {
- --tw-bg-opacity: 1;
- background-color: hsl(var(--foo) 50% 50% / var(--tw-bg-opacity));
- }
- .bg-\[hsl\(var\(--foo\)\,50\%\,50\%\)\]\/50 {
- background-color: hsl(var(--foo) 50% 50% / 0.5);
- }
- .bg-\[hsl\(var\(--foo\)\,var\(--bar\)\,var\(--baz\)\)\]\/50 {
- background-color: hsl(var(--foo) var(--bar) var(--baz) / 0.5);
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .bg-\[hsl\(123\,50\%\,var\(--foo\)\)\] {
- background-color: hsl(123, 50%, var(--foo));
- }
- .bg-\[hsl\(123\,50\%\,var\(--foo\)\)\]\/50 {
- background-color: hsl(123 50% var(--foo) / 0.5);
- }
- .bg-\[hsl\(123\,var\(--foo\)\,50\%\)\] {
- background-color: hsl(123, var(--foo), 50%);
- }
- .bg-\[hsl\(123\,var\(--foo\)\,50\%\)\]\/50 {
- background-color: hsl(123 var(--foo) 50% / 0.5);
- }
- .bg-\[hsl\(var\(--foo\)\,50\%\,50\%\)\] {
- background-color: hsl(var(--foo), 50%, 50%);
- }
- .bg-\[hsl\(var\(--foo\)\,50\%\,50\%\)\]\/50 {
- background-color: hsl(var(--foo) 50% 50% / 0.5);
- }
- .bg-\[hsl\(var\(--foo\)\,var\(--bar\)\,var\(--baz\)\)\]\/50 {
- background-color: hsl(var(--foo) var(--bar) var(--baz) / 0.5);
- }
- `)
- })
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-red-500\/50 {
+ background-color: #ef444480;
+ }
+ `)
+ })
+})
+
+test('colors with slashes are matched first', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ extend: {
+ colors: {
+ 'red-500/50': '#ff0000',
+ },
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-red-500\/50 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 0 0 / var(--tw-bg-opacity));
+ }
+ `)
+ })
+})
+
+test('arbitrary color opacity modifier', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-red-500\/\[var\(--opacity\)\] {
+ background-color: rgb(239 68 68 / var(--opacity));
+ }
+ `)
+ })
+})
+
+test('missing alpha generates nothing', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(``)
+ })
+})
+
+test('arbitrary color with opacity from scale', async () => {
+ let config = {
+ content: [{ raw: 'bg-[wheat]/50' }],
+ theme: {},
+ plugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-\[wheat\]\/50 {
+ background-color: #f5deb380;
+ }
+ `)
+ })
+})
+
+test('arbitrary color with arbitrary opacity', async () => {
+ let config = {
+ content: [{ raw: 'bg-[#bada55]/[0.2]' }],
+ theme: {},
+ plugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-\[\#bada55\]\/\[0\.2\] {
+ background-color: #bada5533;
+ }
+ `)
+ })
+})
+
+test('undefined theme color with opacity from scale', async () => {
+ let config = {
+ content: [{ raw: 'bg-garbage/50' }],
+ theme: {},
+ plugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(``)
+ })
+})
+
+test('values not in the opacity config are ignored', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ opacity: {
+ 0: '0',
+ 25: '0.25',
+ 5: '0.5',
+ 75: '0.75',
+ 100: '1',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(``)
+ })
+})
+
+test('function colors are supported', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ colors: {
+ blue: ({ opacityValue }) => {
+ return `rgba(var(--colors-blue), ${opacityValue})`
+ },
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-blue\/50 {
+ background-color: rgba(var(--colors-blue), 0.5);
+ }
+ `)
+ })
+})
+
+test('utilities that support any type are supported', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+ `,
+ },
+ ],
+ theme: {
+ extend: {
+ fill: (theme) => theme('colors'),
+ },
+ },
+ plugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .from-red-500\/50 {
+ --tw-gradient-from: #ef444480 var(--tw-gradient-from-position);
+ --tw-gradient-to: #ef444400 var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+ }
+ .fill-red-500\/25 {
+ fill: #ef444440;
+ }
+ .placeholder-red-500\/75::placeholder {
+ color: #ef4444bf;
+ }
+ `)
+ })
+})
+
+test('opacity modifier in combination with partial custom properties', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-\[hsl\(123\,50\%\,var\(--foo\)\)\] {
+ --tw-bg-opacity: 1;
+ background-color: hsl(123 50% var(--foo) / var(--tw-bg-opacity));
+ }
+ .bg-\[hsl\(123\,50\%\,var\(--foo\)\)\]\/50 {
+ background-color: hsl(123 50% var(--foo) / 0.5);
+ }
+ .bg-\[hsl\(123\,var\(--foo\)\,50\%\)\] {
+ --tw-bg-opacity: 1;
+ background-color: hsl(123 var(--foo) 50% / var(--tw-bg-opacity));
+ }
+ .bg-\[hsl\(123\,var\(--foo\)\,50\%\)\]\/50 {
+ background-color: hsl(123 var(--foo) 50% / 0.5);
+ }
+ .bg-\[hsl\(var\(--foo\)\,50\%\,50\%\)\] {
+ --tw-bg-opacity: 1;
+ background-color: hsl(var(--foo) 50% 50% / var(--tw-bg-opacity));
+ }
+ .bg-\[hsl\(var\(--foo\)\,50\%\,50\%\)\]\/50 {
+ background-color: hsl(var(--foo) 50% 50% / 0.5);
+ }
+ .bg-\[hsl\(var\(--foo\)\,var\(--bar\)\,var\(--baz\)\)\]\/50 {
+ background-color: hsl(var(--foo) var(--bar) var(--baz) / 0.5);
+ }
+ `)
})
})
diff --git a/tests/color.test.js b/tests/color.test.js
index be1d85924..659a2d045 100644
--- a/tests/color.test.js
+++ b/tests/color.test.js
@@ -1,96 +1,93 @@
import { parseColor, formatColor } from '../src/util/color'
-import { crosscheck } from './util/run'
-crosscheck(() => {
- describe('parseColor', () => {
- it.each`
- color | output
- ${'black'} | ${{ mode: 'rgb', color: ['0', '0', '0'], alpha: undefined }}
- ${'#0088cc'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: undefined }}
- ${'#08c'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: undefined }}
- ${'#0088cc99'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: '0.6' }}
- ${'#08c9'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: '0.6' }}
- ${'rgb(0, 30, 60)'} | ${{ mode: 'rgb', color: ['0', '30', '60'], alpha: undefined }}
- ${'rgba(0, 30, 60, 0.5)'} | ${{ mode: 'rgba', color: ['0', '30', '60'], alpha: '0.5' }}
- ${'rgb(0 30 60)'} | ${{ mode: 'rgb', color: ['0', '30', '60'], alpha: undefined }}
- ${'rgb(0 30 60 / 0.5)'} | ${{ mode: 'rgb', color: ['0', '30', '60'], alpha: '0.5' }}
- ${'rgb(var(--foo), 30, 60)'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', '60'], alpha: undefined }}
- ${'rgb(0, var(--foo), 60)'} | ${{ mode: 'rgb', color: ['0', 'var(--foo)', '60'], alpha: undefined }}
- ${'rgb(0, 30, var(--foo))'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: undefined }}
- ${'rgb(0, 30, var(--foo), 0.5)'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: '0.5' }}
- ${'rgb(var(--foo), 30, var(--bar))'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', 'var(--bar)'], alpha: undefined }}
- ${'rgb(var(--foo), var(--bar), var(--baz))'} | ${{ mode: 'rgb', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
- ${'rgb(var(--foo) 30 60)'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', '60'], alpha: undefined }}
- ${'rgb(0 var(--foo) 60)'} | ${{ mode: 'rgb', color: ['0', 'var(--foo)', '60'], alpha: undefined }}
- ${'rgb(0 30 var(--foo))'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: undefined }}
- ${'rgb(0 30 var(--foo) / 0.5)'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: '0.5' }}
- ${'rgb(var(--foo) 30 var(--bar))'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', 'var(--bar)'], alpha: undefined }}
- ${'rgb(var(--foo) var(--bar) var(--baz))'} | ${{ mode: 'rgb', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
- ${'hsl(0, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0', '30%', '60%'], alpha: undefined }}
- ${'hsl(0deg, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0deg', '30%', '60%'], alpha: undefined }}
- ${'hsl(0rad, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0rad', '30%', '60%'], alpha: undefined }}
- ${'hsl(0grad, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0grad', '30%', '60%'], alpha: undefined }}
- ${'hsl(0turn, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0turn', '30%', '60%'], alpha: undefined }}
- ${'hsla(0, 30%, 60%, 0.5)'} | ${{ mode: 'hsla', color: ['0', '30%', '60%'], alpha: '0.5' }}
- ${'hsla(0deg, 30%, 60%, 0.5)'} | ${{ mode: 'hsla', color: ['0deg', '30%', '60%'], alpha: '0.5' }}
- ${'hsla(0rad, 30%, 60%, 0.5)'} | ${{ mode: 'hsla', color: ['0rad', '30%', '60%'], alpha: '0.5' }}
- ${'hsla(0grad, 30%, 60%, 0.5)'} | ${{ mode: 'hsla', color: ['0grad', '30%', '60%'], alpha: '0.5' }}
- ${'hsla(0turn, 30%, 60%, 0.5)'} | ${{ mode: 'hsla', color: ['0turn', '30%', '60%'], alpha: '0.5' }}
- ${'hsl(0 30% 60%)'} | ${{ mode: 'hsl', color: ['0', '30%', '60%'], alpha: undefined }}
- ${'hsl(0deg 30% 60%)'} | ${{ mode: 'hsl', color: ['0deg', '30%', '60%'], alpha: undefined }}
- ${'hsl(0rad 30% 60%)'} | ${{ mode: 'hsl', color: ['0rad', '30%', '60%'], alpha: undefined }}
- ${'hsl(0grad 30% 60%)'} | ${{ mode: 'hsl', color: ['0grad', '30%', '60%'], alpha: undefined }}
- ${'hsl(0turn 30% 60%)'} | ${{ mode: 'hsl', color: ['0turn', '30%', '60%'], alpha: undefined }}
- ${'hsl(0 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0', '30%', '60%'], alpha: '0.5' }}
- ${'hsl(0deg 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0deg', '30%', '60%'], alpha: '0.5' }}
- ${'hsl(0rad 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0rad', '30%', '60%'], alpha: '0.5' }}
- ${'hsl(0grad 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0grad', '30%', '60%'], alpha: '0.5' }}
- ${'hsl(0turn 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0turn', '30%', '60%'], alpha: '0.5' }}
- ${'hsl(var(--foo), 30%, 60%)'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', '60%'], alpha: undefined }}
- ${'hsl(0, var(--foo), 60%)'} | ${{ mode: 'hsl', color: ['0', 'var(--foo)', '60%'], alpha: undefined }}
- ${'hsl(0, 30%, var(--foo))'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: undefined }}
- ${'hsl(0, 30%, var(--foo), 0.5)'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: '0.5' }}
- ${'hsl(var(--foo), 30%, var(--bar))'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', 'var(--bar)'], alpha: undefined }}
- ${'hsl(var(--foo), var(--bar), var(--baz))'} | ${{ mode: 'hsl', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
- ${'hsl(var(--foo) 30% 60%)'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', '60%'], alpha: undefined }}
- ${'hsl(0 var(--foo) 60%)'} | ${{ mode: 'hsl', color: ['0', 'var(--foo)', '60%'], alpha: undefined }}
- ${'hsl(0 30% var(--foo))'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: undefined }}
- ${'hsl(0 30% var(--foo) / 0.5)'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: '0.5' }}
- ${'hsl(var(--foo) 30% var(--bar))'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', 'var(--bar)'], alpha: undefined }}
- ${'hsl(var(--foo) var(--bar) var(--baz))'} | ${{ mode: 'hsl', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
- ${'rgba(var(--foo), 0.1)'} | ${{ mode: 'rgba', color: ['var(--foo)'], alpha: '0.1' }}
- ${'rgba(var(--foo), var(--alpha))'} | ${{ mode: 'rgba', color: ['var(--foo)'], alpha: 'var(--alpha)' }}
- ${'hsla(var(--foo), 0.1)'} | ${{ mode: 'hsla', color: ['var(--foo)'], alpha: '0.1' }}
- ${'hsla(var(--foo), var(--alpha))'} | ${{ mode: 'hsla', color: ['var(--foo)'], alpha: 'var(--alpha)' }}
- ${'transparent'} | ${{ mode: 'rgb', color: ['0', '0', '0'], alpha: '0' }}
- `('should parse "$color" to the correct value', ({ color, output }) => {
- expect(parseColor(color)).toEqual(output)
- })
-
- it.each`
- color
- ${'var(--my-color)'}
- ${'currentColor'}
- ${'inherit'}
- ${'initial'}
- ${'revert'}
- ${'unset'}
- `('should return `null` for unparseable color "$color"', ({ color }) => {
- expect(parseColor(color)).toBe(null)
- })
+describe('parseColor', () => {
+ it.each`
+ color | output
+ ${'black'} | ${{ mode: 'rgb', color: ['0', '0', '0'], alpha: undefined }}
+ ${'#0088cc'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: undefined }}
+ ${'#08c'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: undefined }}
+ ${'#0088cc99'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: '0.6' }}
+ ${'#08c9'} | ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: '0.6' }}
+ ${'rgb(0, 30, 60)'} | ${{ mode: 'rgb', color: ['0', '30', '60'], alpha: undefined }}
+ ${'rgba(0, 30, 60, 0.5)'} | ${{ mode: 'rgba', color: ['0', '30', '60'], alpha: '0.5' }}
+ ${'rgb(0 30 60)'} | ${{ mode: 'rgb', color: ['0', '30', '60'], alpha: undefined }}
+ ${'rgb(0 30 60 / 0.5)'} | ${{ mode: 'rgb', color: ['0', '30', '60'], alpha: '0.5' }}
+ ${'rgb(var(--foo), 30, 60)'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', '60'], alpha: undefined }}
+ ${'rgb(0, var(--foo), 60)'} | ${{ mode: 'rgb', color: ['0', 'var(--foo)', '60'], alpha: undefined }}
+ ${'rgb(0, 30, var(--foo))'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: undefined }}
+ ${'rgb(0, 30, var(--foo), 0.5)'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: '0.5' }}
+ ${'rgb(var(--foo), 30, var(--bar))'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', 'var(--bar)'], alpha: undefined }}
+ ${'rgb(var(--foo), var(--bar), var(--baz))'} | ${{ mode: 'rgb', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
+ ${'rgb(var(--foo) 30 60)'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', '60'], alpha: undefined }}
+ ${'rgb(0 var(--foo) 60)'} | ${{ mode: 'rgb', color: ['0', 'var(--foo)', '60'], alpha: undefined }}
+ ${'rgb(0 30 var(--foo))'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: undefined }}
+ ${'rgb(0 30 var(--foo) / 0.5)'} | ${{ mode: 'rgb', color: ['0', '30', 'var(--foo)'], alpha: '0.5' }}
+ ${'rgb(var(--foo) 30 var(--bar))'} | ${{ mode: 'rgb', color: ['var(--foo)', '30', 'var(--bar)'], alpha: undefined }}
+ ${'rgb(var(--foo) var(--bar) var(--baz))'} | ${{ mode: 'rgb', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
+ ${'hsl(0, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0', '30%', '60%'], alpha: undefined }}
+ ${'hsl(0deg, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0deg', '30%', '60%'], alpha: undefined }}
+ ${'hsl(0rad, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0rad', '30%', '60%'], alpha: undefined }}
+ ${'hsl(0grad, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0grad', '30%', '60%'], alpha: undefined }}
+ ${'hsl(0turn, 30%, 60%)'} | ${{ mode: 'hsl', color: ['0turn', '30%', '60%'], alpha: undefined }}
+ ${'hsla(0, 30%, 60%, 0.5)'} | ${{ mode: 'hsla', color: ['0', '30%', '60%'], alpha: '0.5' }}
+ ${'hsla(0deg, 30%, 60%, 0.5)'} | ${{ mode: 'hsla', color: ['0deg', '30%', '60%'], alpha: '0.5' }}
+ ${'hsla(0rad, 30%, 60%, 0.5)'} | ${{ mode: 'hsla', color: ['0rad', '30%', '60%'], alpha: '0.5' }}
+ ${'hsla(0grad, 30%, 60%, 0.5)'} | ${{ mode: 'hsla', color: ['0grad', '30%', '60%'], alpha: '0.5' }}
+ ${'hsla(0turn, 30%, 60%, 0.5)'} | ${{ mode: 'hsla', color: ['0turn', '30%', '60%'], alpha: '0.5' }}
+ ${'hsl(0 30% 60%)'} | ${{ mode: 'hsl', color: ['0', '30%', '60%'], alpha: undefined }}
+ ${'hsl(0deg 30% 60%)'} | ${{ mode: 'hsl', color: ['0deg', '30%', '60%'], alpha: undefined }}
+ ${'hsl(0rad 30% 60%)'} | ${{ mode: 'hsl', color: ['0rad', '30%', '60%'], alpha: undefined }}
+ ${'hsl(0grad 30% 60%)'} | ${{ mode: 'hsl', color: ['0grad', '30%', '60%'], alpha: undefined }}
+ ${'hsl(0turn 30% 60%)'} | ${{ mode: 'hsl', color: ['0turn', '30%', '60%'], alpha: undefined }}
+ ${'hsl(0 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0', '30%', '60%'], alpha: '0.5' }}
+ ${'hsl(0deg 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0deg', '30%', '60%'], alpha: '0.5' }}
+ ${'hsl(0rad 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0rad', '30%', '60%'], alpha: '0.5' }}
+ ${'hsl(0grad 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0grad', '30%', '60%'], alpha: '0.5' }}
+ ${'hsl(0turn 30% 60% / 0.5)'} | ${{ mode: 'hsl', color: ['0turn', '30%', '60%'], alpha: '0.5' }}
+ ${'hsl(var(--foo), 30%, 60%)'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', '60%'], alpha: undefined }}
+ ${'hsl(0, var(--foo), 60%)'} | ${{ mode: 'hsl', color: ['0', 'var(--foo)', '60%'], alpha: undefined }}
+ ${'hsl(0, 30%, var(--foo))'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: undefined }}
+ ${'hsl(0, 30%, var(--foo), 0.5)'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: '0.5' }}
+ ${'hsl(var(--foo), 30%, var(--bar))'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', 'var(--bar)'], alpha: undefined }}
+ ${'hsl(var(--foo), var(--bar), var(--baz))'} | ${{ mode: 'hsl', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
+ ${'hsl(var(--foo) 30% 60%)'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', '60%'], alpha: undefined }}
+ ${'hsl(0 var(--foo) 60%)'} | ${{ mode: 'hsl', color: ['0', 'var(--foo)', '60%'], alpha: undefined }}
+ ${'hsl(0 30% var(--foo))'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: undefined }}
+ ${'hsl(0 30% var(--foo) / 0.5)'} | ${{ mode: 'hsl', color: ['0', '30%', 'var(--foo)'], alpha: '0.5' }}
+ ${'hsl(var(--foo) 30% var(--bar))'} | ${{ mode: 'hsl', color: ['var(--foo)', '30%', 'var(--bar)'], alpha: undefined }}
+ ${'hsl(var(--foo) var(--bar) var(--baz))'} | ${{ mode: 'hsl', color: ['var(--foo)', 'var(--bar)', 'var(--baz)'], alpha: undefined }}
+ ${'rgba(var(--foo), 0.1)'} | ${{ mode: 'rgba', color: ['var(--foo)'], alpha: '0.1' }}
+ ${'rgba(var(--foo), var(--alpha))'} | ${{ mode: 'rgba', color: ['var(--foo)'], alpha: 'var(--alpha)' }}
+ ${'hsla(var(--foo), 0.1)'} | ${{ mode: 'hsla', color: ['var(--foo)'], alpha: '0.1' }}
+ ${'hsla(var(--foo), var(--alpha))'} | ${{ mode: 'hsla', color: ['var(--foo)'], alpha: 'var(--alpha)' }}
+ ${'transparent'} | ${{ mode: 'rgb', color: ['0', '0', '0'], alpha: '0' }}
+ `('should parse "$color" to the correct value', ({ color, output }) => {
+ expect(parseColor(color)).toEqual(output)
})
- describe('formatColor', () => {
- it.each`
- color | output
- ${{ mode: 'rgb', color: ['0', '0', '0'], alpha: undefined }} | ${'rgb(0 0 0)'}
- ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: undefined }} | ${'rgb(0 136 204)'}
- ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: '0.6' }} | ${'rgb(0 136 204 / 0.6)'}
- ${{ mode: 'hsl', color: ['0', '0%', '0%'], alpha: undefined }} | ${'hsl(0 0% 0%)'}
- ${{ mode: 'hsl', color: ['0', '136%', '204%'], alpha: undefined }} | ${'hsl(0 136% 204%)'}
- ${{ mode: 'hsl', color: ['0', '136%', '204%'], alpha: '0.6' }} | ${'hsl(0 136% 204% / 0.6)'}
- `('should format the color pieces into a proper "$output"', ({ color, output }) => {
- expect(formatColor(color)).toEqual(output)
- })
+ it.each`
+ color
+ ${'var(--my-color)'}
+ ${'currentColor'}
+ ${'inherit'}
+ ${'initial'}
+ ${'revert'}
+ ${'unset'}
+ `('should return `null` for unparseable color "$color"', ({ color }) => {
+ expect(parseColor(color)).toBe(null)
+ })
+})
+
+describe('formatColor', () => {
+ it.each`
+ color | output
+ ${{ mode: 'rgb', color: ['0', '0', '0'], alpha: undefined }} | ${'rgb(0 0 0)'}
+ ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: undefined }} | ${'rgb(0 136 204)'}
+ ${{ mode: 'rgb', color: ['0', '136', '204'], alpha: '0.6' }} | ${'rgb(0 136 204 / 0.6)'}
+ ${{ mode: 'hsl', color: ['0', '0%', '0%'], alpha: undefined }} | ${'hsl(0 0% 0%)'}
+ ${{ mode: 'hsl', color: ['0', '136%', '204%'], alpha: undefined }} | ${'hsl(0 136% 204%)'}
+ ${{ mode: 'hsl', color: ['0', '136%', '204%'], alpha: '0.6' }} | ${'hsl(0 136% 204% / 0.6)'}
+ `('should format the color pieces into a proper "$output"', ({ color, output }) => {
+ expect(formatColor(color)).toEqual(output)
})
})
diff --git a/tests/combined-selectors.test.js b/tests/combined-selectors.test.js
index 51aa0a8ce..b3e8a333f 100644
--- a/tests/combined-selectors.test.js
+++ b/tests/combined-selectors.test.js
@@ -1,76 +1,74 @@
-import { crosscheck, run, html, css, defaults } from './util/run'
+import { run, html, css, defaults } from './util/run'
-crosscheck(() => {
- it('should generate the partial selector, if only a partial is used (base layer)', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
+it('should generate the partial selector, if only a partial is used (base layer)', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind base;
+ let input = css`
+ @tailwind base;
- @layer base {
- :root {
- font-weight: bold;
- }
-
- /* --- */
-
- :root,
- .a {
- color: black;
- }
+ @layer base {
+ :root {
+ font-weight: bold;
}
- `
- return run(input, config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- :root {
- font-weight: bold;
- }
- :root,
- .a {
- color: #000;
- }
- ${defaults}
- `)
- })
- })
+ /* --- */
- it('should generate the partial selector, if only a partial is used (utilities layer)', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
-
- @layer utilities {
- :root {
- font-weight: bold;
- }
-
- /* --- */
-
- :root,
- .a {
- color: black;
- }
+ :root,
+ .a {
+ color: black;
}
- `
+ }
+ `
- return run(input, config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- :root {
- font-weight: bold;
- }
- :root,
- .a {
- color: #000;
- }
- `)
- })
+ return run(input, config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ :root {
+ font-weight: bold;
+ }
+ :root,
+ .a {
+ color: #000;
+ }
+ ${defaults}
+ `)
+ })
+})
+
+it('should generate the partial selector, if only a partial is used (utilities layer)', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+
+ @layer utilities {
+ :root {
+ font-weight: bold;
+ }
+
+ /* --- */
+
+ :root,
+ .a {
+ color: black;
+ }
+ }
+ `
+
+ return run(input, config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ :root {
+ font-weight: bold;
+ }
+ :root,
+ .a {
+ color: #000;
+ }
+ `)
})
})
diff --git a/tests/configurePlugins.test.js b/tests/configurePlugins.test.js
index 367ab223f..490cddc03 100644
--- a/tests/configurePlugins.test.js
+++ b/tests/configurePlugins.test.js
@@ -1,28 +1,25 @@
import configurePlugins from '../src/util/configurePlugins'
-import { crosscheck } from './util/run'
-crosscheck(() => {
- test('setting a plugin to false removes it', () => {
- const plugins = ['fontSize', 'display', 'backgroundPosition']
+test('setting a plugin to false removes it', () => {
+ const plugins = ['fontSize', 'display', 'backgroundPosition']
- const configuredPlugins = configurePlugins({ display: false }, plugins)
+ const configuredPlugins = configurePlugins({ display: false }, plugins)
- expect(configuredPlugins).toEqual(['fontSize', 'backgroundPosition'])
- })
-
- test('passing only false removes all plugins', () => {
- const plugins = ['fontSize', 'display', 'backgroundPosition']
-
- const configuredPlugins = configurePlugins(false, plugins)
-
- expect(configuredPlugins).toEqual([])
- })
-
- test('passing an array safelists plugins', () => {
- const plugins = ['fontSize', 'display', 'backgroundPosition']
-
- const configuredPlugins = configurePlugins(['display'], plugins)
-
- expect(configuredPlugins).toEqual(['display'])
- })
+ expect(configuredPlugins).toEqual(['fontSize', 'backgroundPosition'])
+})
+
+test('passing only false removes all plugins', () => {
+ const plugins = ['fontSize', 'display', 'backgroundPosition']
+
+ const configuredPlugins = configurePlugins(false, plugins)
+
+ expect(configuredPlugins).toEqual([])
+})
+
+test('passing an array safelists plugins', () => {
+ const plugins = ['fontSize', 'display', 'backgroundPosition']
+
+ const configuredPlugins = configurePlugins(['display'], plugins)
+
+ expect(configuredPlugins).toEqual(['display'])
})
diff --git a/tests/containerPlugin.test.js b/tests/containerPlugin.test.js
index 428a807d0..4b334e90c 100644
--- a/tests/containerPlugin.test.js
+++ b/tests/containerPlugin.test.js
@@ -1,347 +1,345 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(({}) => {
- test('options are not required', () => {
- let config = { content: [{ raw: html`
` }] }
+test('options are not required', () => {
+ let config = { content: [{ raw: html`
` }] }
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .container {
+ width: 100%;
+ }
+ @media (min-width: 640px) {
.container {
- width: 100%;
+ max-width: 640px;
}
- @media (min-width: 640px) {
- .container {
- max-width: 640px;
- }
- }
- @media (min-width: 768px) {
- .container {
- max-width: 768px;
- }
- }
- @media (min-width: 1024px) {
- .container {
- max-width: 1024px;
- }
- }
- @media (min-width: 1280px) {
- .container {
- max-width: 1280px;
- }
- }
- @media (min-width: 1536px) {
- .container {
- max-width: 1536px;
- }
- }
- `)
- })
- })
-
- test('screens can be passed explicitly', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- container: {
- screens: ['400px', '500px'],
- },
- },
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
+ }
+ @media (min-width: 768px) {
.container {
- width: 100%;
+ max-width: 768px;
}
- @media (min-width: 400px) {
- .container {
- max-width: 400px;
- }
- }
- @media (min-width: 500px) {
- .container {
- max-width: 500px;
- }
- }
- `)
- })
- })
-
- test('screens are ordered ascending by min-width', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- container: {
- screens: ['500px', '400px'],
- },
- },
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
+ }
+ @media (min-width: 1024px) {
.container {
- width: 100%;
+ max-width: 1024px;
}
- @media (min-width: 400px) {
- .container {
- max-width: 400px;
- }
- }
- @media (min-width: 500px) {
- .container {
- max-width: 500px;
- }
- }
- `)
- })
- })
-
- test('screens are deduplicated by min-width', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- container: {
- screens: {
- sm: '576px',
- md: '768px',
- 'sm-only': { min: '576px', max: '767px' },
- },
- },
- },
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
+ }
+ @media (min-width: 1280px) {
.container {
- width: 100%;
+ max-width: 1280px;
}
- @media (min-width: 576px) {
- .container {
- max-width: 576px;
- }
- }
- @media (min-width: 768px) {
- .container {
- max-width: 768px;
- }
- }
- `)
- })
- })
-
- test('the container can be centered by default', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- container: {
- center: true,
- },
- },
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
+ }
+ @media (min-width: 1536px) {
.container {
- width: 100%;
- margin-left: auto;
- margin-right: auto;
+ max-width: 1536px;
}
- @media (min-width: 640px) {
- .container {
- max-width: 640px;
- }
- }
- @media (min-width: 768px) {
- .container {
- max-width: 768px;
- }
- }
- @media (min-width: 1024px) {
- .container {
- max-width: 1024px;
- }
- }
- @media (min-width: 1280px) {
- .container {
- max-width: 1280px;
- }
- }
- @media (min-width: 1536px) {
- .container {
- max-width: 1536px;
- }
- }
- `)
- })
- })
-
- test('horizontal padding can be included by default', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- container: {
- padding: '2rem',
- },
- },
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .container {
- width: 100%;
- padding-left: 2rem;
- padding-right: 2rem;
- }
- @media (min-width: 640px) {
- .container {
- max-width: 640px;
- }
- }
- @media (min-width: 768px) {
- .container {
- max-width: 768px;
- }
- }
- @media (min-width: 1024px) {
- .container {
- max-width: 1024px;
- }
- }
- @media (min-width: 1280px) {
- .container {
- max-width: 1280px;
- }
- }
- @media (min-width: 1536px) {
- .container {
- max-width: 1536px;
- }
- }
- `)
- })
- })
-
- test('responsive horizontal padding can be included by default', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- screens: {
- sm: '576px',
- md: { min: '768px' },
- lg: { 'min-width': '992px' },
- xl: { min: '1200px', max: '1600px' },
- },
- container: {
- padding: {
- DEFAULT: '1rem',
- sm: '2rem',
- lg: '4rem',
- xl: '5rem',
- },
- },
- },
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .container {
- width: 100%;
- padding-left: 1rem;
- padding-right: 1rem;
- }
- @media (min-width: 576px) {
- .container {
- max-width: 576px;
- padding-left: 2rem;
- padding-right: 2rem;
- }
- }
- @media (min-width: 768px) {
- .container {
- max-width: 768px;
- }
- }
- @media (min-width: 992px) {
- .container {
- max-width: 992px;
- padding-left: 4rem;
- padding-right: 4rem;
- }
- }
- @media (min-width: 1200px) {
- .container {
- max-width: 1200px;
- padding-left: 5rem;
- padding-right: 5rem;
- }
- }
- `)
- })
- })
-
- test('setting all options at once', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- container: {
- screens: ['400px', '500px'],
- center: true,
- padding: '2rem',
- },
- },
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .container {
- width: 100%;
- margin-left: auto;
- margin-right: auto;
- padding-left: 2rem;
- padding-right: 2rem;
- }
- @media (min-width: 400px) {
- .container {
- max-width: 400px;
- }
- }
- @media (min-width: 500px) {
- .container {
- max-width: 500px;
- }
- }
- `)
- })
- })
-
- test('container can use variants', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- container: {
- screens: ['400px', '500px'],
- },
- },
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 1024px) {
- .lg\:hover\:container:hover {
- width: 100%;
- }
- @media (min-width: 400px) {
- .lg\:hover\:container:hover {
- max-width: 400px;
- }
- }
- @media (min-width: 500px) {
- .lg\:hover\:container:hover {
- max-width: 500px;
- }
- }
- }
- `)
- })
+ }
+ `)
+ })
+})
+
+test('screens can be passed explicitly', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ container: {
+ screens: ['400px', '500px'],
+ },
+ },
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .container {
+ width: 100%;
+ }
+ @media (min-width: 400px) {
+ .container {
+ max-width: 400px;
+ }
+ }
+ @media (min-width: 500px) {
+ .container {
+ max-width: 500px;
+ }
+ }
+ `)
+ })
+})
+
+test('screens are ordered ascending by min-width', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ container: {
+ screens: ['500px', '400px'],
+ },
+ },
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .container {
+ width: 100%;
+ }
+ @media (min-width: 400px) {
+ .container {
+ max-width: 400px;
+ }
+ }
+ @media (min-width: 500px) {
+ .container {
+ max-width: 500px;
+ }
+ }
+ `)
+ })
+})
+
+test('screens are deduplicated by min-width', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ container: {
+ screens: {
+ sm: '576px',
+ md: '768px',
+ 'sm-only': { min: '576px', max: '767px' },
+ },
+ },
+ },
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .container {
+ width: 100%;
+ }
+ @media (min-width: 576px) {
+ .container {
+ max-width: 576px;
+ }
+ }
+ @media (min-width: 768px) {
+ .container {
+ max-width: 768px;
+ }
+ }
+ `)
+ })
+})
+
+test('the container can be centered by default', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ container: {
+ center: true,
+ },
+ },
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .container {
+ width: 100%;
+ margin-left: auto;
+ margin-right: auto;
+ }
+ @media (min-width: 640px) {
+ .container {
+ max-width: 640px;
+ }
+ }
+ @media (min-width: 768px) {
+ .container {
+ max-width: 768px;
+ }
+ }
+ @media (min-width: 1024px) {
+ .container {
+ max-width: 1024px;
+ }
+ }
+ @media (min-width: 1280px) {
+ .container {
+ max-width: 1280px;
+ }
+ }
+ @media (min-width: 1536px) {
+ .container {
+ max-width: 1536px;
+ }
+ }
+ `)
+ })
+})
+
+test('horizontal padding can be included by default', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ container: {
+ padding: '2rem',
+ },
+ },
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .container {
+ width: 100%;
+ padding-left: 2rem;
+ padding-right: 2rem;
+ }
+ @media (min-width: 640px) {
+ .container {
+ max-width: 640px;
+ }
+ }
+ @media (min-width: 768px) {
+ .container {
+ max-width: 768px;
+ }
+ }
+ @media (min-width: 1024px) {
+ .container {
+ max-width: 1024px;
+ }
+ }
+ @media (min-width: 1280px) {
+ .container {
+ max-width: 1280px;
+ }
+ }
+ @media (min-width: 1536px) {
+ .container {
+ max-width: 1536px;
+ }
+ }
+ `)
+ })
+})
+
+test('responsive horizontal padding can be included by default', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ screens: {
+ sm: '576px',
+ md: { min: '768px' },
+ lg: { 'min-width': '992px' },
+ xl: { min: '1200px', max: '1600px' },
+ },
+ container: {
+ padding: {
+ DEFAULT: '1rem',
+ sm: '2rem',
+ lg: '4rem',
+ xl: '5rem',
+ },
+ },
+ },
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .container {
+ width: 100%;
+ padding-left: 1rem;
+ padding-right: 1rem;
+ }
+ @media (min-width: 576px) {
+ .container {
+ max-width: 576px;
+ padding-left: 2rem;
+ padding-right: 2rem;
+ }
+ }
+ @media (min-width: 768px) {
+ .container {
+ max-width: 768px;
+ }
+ }
+ @media (min-width: 992px) {
+ .container {
+ max-width: 992px;
+ padding-left: 4rem;
+ padding-right: 4rem;
+ }
+ }
+ @media (min-width: 1200px) {
+ .container {
+ max-width: 1200px;
+ padding-left: 5rem;
+ padding-right: 5rem;
+ }
+ }
+ `)
+ })
+})
+
+test('setting all options at once', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ container: {
+ screens: ['400px', '500px'],
+ center: true,
+ padding: '2rem',
+ },
+ },
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .container {
+ width: 100%;
+ margin-left: auto;
+ margin-right: auto;
+ padding-left: 2rem;
+ padding-right: 2rem;
+ }
+ @media (min-width: 400px) {
+ .container {
+ max-width: 400px;
+ }
+ }
+ @media (min-width: 500px) {
+ .container {
+ max-width: 500px;
+ }
+ }
+ `)
+ })
+})
+
+test('container can use variants', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ container: {
+ screens: ['400px', '500px'],
+ },
+ },
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 1024px) {
+ .lg\:hover\:container:hover {
+ width: 100%;
+ }
+ @media (min-width: 400px) {
+ .lg\:hover\:container:hover {
+ max-width: 400px;
+ }
+ }
+ @media (min-width: 500px) {
+ .lg\:hover\:container:hover {
+ max-width: 500px;
+ }
+ }
+ }
+ `)
})
})
diff --git a/tests/context-reuse.test.js b/tests/context-reuse.test.js
index 4ead43fd4..7cd006cde 100644
--- a/tests/context-reuse.test.js
+++ b/tests/context-reuse.test.js
@@ -1,4 +1,4 @@
-import { crosscheck, css } from './util/run'
+import { css } from './util/run'
const fs = require('fs')
const path = require('path')
@@ -7,126 +7,122 @@ const tailwind = require('../src/index.js')
const sharedState = require('../src/lib/sharedState.js')
const configPath = path.resolve(__dirname, './context-reuse.tailwind.config.js')
-crosscheck(({ stable, oxide }) => {
- function run(input, config = {}, from = null) {
- let { currentTestName } = expect.getState()
+function run(input, config = {}, from = null) {
+ let { currentTestName } = expect.getState()
- from = `${path.resolve(__filename)}?test=${currentTestName}&${from}`
+ from = `${path.resolve(__filename)}?test=${currentTestName}&${from}`
- return postcss(tailwind(config)).process(input, { from })
+ return postcss(tailwind(config)).process(input, { from })
+}
+
+beforeEach(async () => {
+ let config = {
+ content: [path.resolve(__dirname, './context-reuse.test.html')],
+ corePlugins: { preflight: false },
}
- beforeEach(async () => {
- let config = {
- content: [path.resolve(__dirname, './context-reuse.test.html')],
- corePlugins: { preflight: false },
+ await fs.promises.writeFile(configPath, `module.exports = ${JSON.stringify(config)};`)
+})
+
+afterEach(async () => {
+ await fs.promises.unlink(configPath)
+})
+
+test('re-uses the context across multiple files with the same config', async () => {
+ let results = [
+ await run(`@tailwind utilities;`, configPath, `id=1`),
+
+ // Using @apply directives should still re-use the context
+ // They depend on the config but do not the other way around
+ await run(`body { @apply bg-blue-400; }`, configPath, `id=2`),
+ await run(`body { @apply text-red-400; }`, configPath, `id=3`),
+ await run(`body { @apply mb-4; }`, configPath, `id=4`),
+ ]
+
+ let dependencies = results.map((result) => {
+ return result.messages
+ .filter((message) => message.type === 'dependency')
+ .map((message) => message.file)
+ })
+
+ // The content files don't have any utilities in them so this should be empty
+ expect(results[0].css).toMatchFormattedCss(css``)
+
+ // However, @apply is being used so we want to verify that they're being inlined into the CSS rules
+ expect(results[1].css).toMatchFormattedCss(css`
+ body {
+ --tw-bg-opacity: 1;
+ background-color: rgb(96 165 250 / var(--tw-bg-opacity));
}
+ `)
- await fs.promises.writeFile(configPath, `module.exports = ${JSON.stringify(config)};`)
- })
+ expect(results[2].css).toMatchFormattedCss(css`
+ body {
+ --tw-text-opacity: 1;
+ color: rgb(248 113 113 / var(--tw-text-opacity));
+ }
+ `)
- afterEach(async () => {
- await fs.promises.unlink(configPath)
- })
+ expect(results[3].css).toMatchFormattedCss(css`
+ body {
+ margin-bottom: 1rem;
+ }
+ `)
- oxide.test.todo('re-uses the context across multiple files with the same config')
- stable.test('re-uses the context across multiple files with the same config', async () => {
- let results = [
- await run(`@tailwind utilities;`, configPath, `id=1`),
+ // Files with @tailwind directives depends on the PostCSS tree, config, AND any content files
+ expect(dependencies[0]).toEqual([
+ path.resolve(__dirname, 'context-reuse.test.html'),
+ path.resolve(__dirname, 'context-reuse.tailwind.config.js'),
+ ])
- // Using @apply directives should still re-use the context
- // They depend on the config but do not the other way around
- await run(`body { @apply bg-blue-400; }`, configPath, `id=2`),
- await run(`body { @apply text-red-400; }`, configPath, `id=3`),
- await run(`body { @apply mb-4; }`, configPath, `id=4`),
- ]
+ // @apply depends only on the containing PostCSS tree *and* the config file but no content files
+ // as they cannot affect the outcome of the @apply directives
+ expect(dependencies[1]).toEqual([path.resolve(__dirname, 'context-reuse.tailwind.config.js')])
- let dependencies = results.map((result) => {
- return result.messages
- .filter((message) => message.type === 'dependency')
- .map((message) => message.file)
- })
+ expect(dependencies[2]).toEqual([path.resolve(__dirname, 'context-reuse.tailwind.config.js')])
- // The content files don't have any utilities in them so this should be empty
- expect(results[0].css).toMatchFormattedCss(css``)
+ expect(dependencies[3]).toEqual([path.resolve(__dirname, 'context-reuse.tailwind.config.js')])
- // However, @apply is being used so we want to verify that they're being inlined into the CSS rules
- expect(results[1].css).toMatchFormattedCss(css`
- body {
- --tw-bg-opacity: 1;
- background-color: rgb(96 165 250 / var(--tw-bg-opacity));
- }
- `)
+ // And none of this should have resulted in multiple contexts being created
+ expect(sharedState.contextSourcesMap.size).toBe(1)
+})
- expect(results[2].css).toMatchFormattedCss(css`
- body {
- --tw-text-opacity: 1;
- color: rgb(248 113 113 / var(--tw-text-opacity));
- }
- `)
+test('updates layers when any CSS containing @tailwind directives changes', async () => {
+ let result
- expect(results[3].css).toMatchFormattedCss(css`
- body {
- margin-bottom: 1rem;
- }
- `)
-
- // Files with @tailwind directives depends on the PostCSS tree, config, AND any content files
- expect(dependencies[0]).toEqual([
- path.resolve(__dirname, 'context-reuse.test.html'),
- path.resolve(__dirname, 'context-reuse.tailwind.config.js'),
- ])
-
- // @apply depends only on the containing PostCSS tree *and* the config file but no content files
- // as they cannot affect the outcome of the @apply directives
- expect(dependencies[1]).toEqual([path.resolve(__dirname, 'context-reuse.tailwind.config.js')])
-
- expect(dependencies[2]).toEqual([path.resolve(__dirname, 'context-reuse.tailwind.config.js')])
-
- expect(dependencies[3]).toEqual([path.resolve(__dirname, 'context-reuse.tailwind.config.js')])
-
- // And none of this should have resulted in multiple contexts being created
- expect(sharedState.contextSourcesMap.size).toBe(1)
- })
-
- oxide.test.todo('updates layers when any CSS containing @tailwind directives changes')
- stable.test('updates layers when any CSS containing @tailwind directives changes', async () => {
- let result
-
- // Compile the initial version once
- let input = css`
- @tailwind utilities;
- @layer utilities {
- .custom-utility {
- color: orange;
- }
- }
- `
-
- result = await run(input, configPath, `id=1`)
-
- expect(result.css).toMatchFormattedCss(css`
- .only\:custom-utility:only-child {
+ // Compile the initial version once
+ let input = css`
+ @tailwind utilities;
+ @layer utilities {
+ .custom-utility {
color: orange;
}
- `)
+ }
+ `
- // Save the file with a change
- input = css`
- @tailwind utilities;
- @layer utilities {
- .custom-utility {
- color: blue;
- }
+ result = await run(input, configPath, `id=1`)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .only\:custom-utility:only-child {
+ color: orange;
+ }
+ `)
+
+ // Save the file with a change
+ input = css`
+ @tailwind utilities;
+ @layer utilities {
+ .custom-utility {
+ color: blue;
}
- `
+ }
+ `
- result = await run(input, configPath, `id=1`)
+ result = await run(input, configPath, `id=1`)
- expect(result.css).toMatchFormattedCss(css`
- .only\:custom-utility:only-child {
- color: #00f;
- }
- `)
- })
+ expect(result.css).toMatchFormattedCss(css`
+ .only\:custom-utility:only-child {
+ color: #00f;
+ }
+ `)
})
diff --git a/tests/css-configuration.test.js b/tests/css-configuration.test.js
new file mode 100644
index 000000000..76803bdc8
--- /dev/null
+++ b/tests/css-configuration.test.js
@@ -0,0 +1,137 @@
+import { run, html, css } from './util/run'
+
+test('it does something', () => {
+ let config = {
+ darkMode: 'class',
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ }
+
+ return run(
+ css`
+ @tailwind utilities;
+
+ :theme {
+ --colors: unset;
+ --colors-mint-50: #f3fdfb;
+ --colors-mint-900: #043820;
+ --colors-plum-50: #f8f6fe;
+ --colors-plum-900: #120c64;
+ --colors-primary: theme(colors.mint.50);
+
+ --font-family-display: Graphik, sans-serif;
+ --font-family-display--font-feature-settings: "cv11", "ss01";
+
+ --font-size-10xl: 10rem;
+ --font-size-10xl--line-height: 10rem;
+
+ --spacing-1\/4: 25%;
+ --spacing-1\/2: 50%;
+ --spacing-3\/4: 75%;
+ --spacing-\32 xl: 20rem;
+
+ --shadow--default: 0 3px 5px -2px hsl(220 3% 15% / 4%), 0 7px 14px -5px hsl(220
+ 3% 15% / 6%);
+
+ --animation-flash: flash 1s;
+ }
+ `,
+ config
+ ).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .animate-flash {
+ animation: 1s flash;
+ }
+ .px-1\/2 {
+ padding-left: 50%;
+ padding-right: 50%;
+ }
+ .px-1\/4 {
+ padding-left: 25%;
+ padding-right: 25%;
+ }
+ .px-2xl {
+ padding-left: 20rem;
+ padding-right: 20rem;
+ }
+ .px-3\/4 {
+ padding-left: 75%;
+ padding-right: 75%;
+ }
+ .font-display {
+ font-feature-settings: "cv11", "ss01";
+ font-family: Graphik, sans-serif;
+ }
+ .text-10xl {
+ font-size: 10rem;
+ line-height: 10rem;
+ }
+ .text-mint-50 {
+ --tw-text-opacity: 1;
+ color: rgb(243 253 251 / var(--tw-text-opacity));
+ }
+ .text-mint-900 {
+ --tw-text-opacity: 1;
+ color: rgb(4 56 32 / var(--tw-text-opacity));
+ }
+ .text-plum-50 {
+ --tw-text-opacity: 1;
+ color: rgb(248 246 254 / var(--tw-text-opacity));
+ }
+ .text-plum-900 {
+ --tw-text-opacity: 1;
+ color: rgb(18 12 100 / var(--tw-text-opacity));
+ }
+ .text-primary {
+ color: #f3fdfb;
+ }
+ .shadow {
+ --tw-shadow: 0 3px 5px -2px #2526270a, 0 7px 14px -5px #2526270f;
+ --tw-shadow-colored: 0 3px 5px -2px var(--tw-shadow-color), 0 7px 14px -5px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
+ var(--tw-shadow);
+ }
+ :root {
+ --colors-mint-50: #f3fdfb;
+ --colors-mint-900: #043820;
+ --colors-plum-50: #f8f6fe;
+ --colors-plum-900: #120c64;
+ --colors-primary: #f3fdfb;
+ --font-family-display: Graphik, sans-serif;
+ --font-family-display--font-feature-settings: "cv11", "ss01";
+ --font-size-10xl: 10rem;
+ --font-size-10xl--line-height: 10rem;
+ --spacing-1\/4: 25%;
+ --spacing-1\/2: 50%;
+ --spacing-3\/4: 75%;
+ --spacing-2xl: 20rem;
+ --shadow--default: 0 3px 5px -2px #2526270a, 0 7px 14px -5px #2526270f;
+ --animation-flash: flash 1s;
+ }
+ `)
+ })
+})
diff --git a/tests/custom-extractors.test.js b/tests/custom-extractors.test.js
index 089084e77..f5933b7a5 100644
--- a/tests/custom-extractors.test.js
+++ b/tests/custom-extractors.test.js
@@ -1,4 +1,4 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
function customExtractor(content) {
let matches = content.match(/class="([^"]+)"/)
@@ -21,117 +21,109 @@ let expected = css`
}
`
-crosscheck(({ stable, oxide }) => {
- describe('modern', () => {
- oxide.test.todo('extract.DEFAULT')
- stable.test('extract.DEFAULT', () => {
- let config = {
- content: {
- files: [{ raw: sharedHtml }],
- extract: {
- DEFAULT: customExtractor,
- },
+describe('modern', () => {
+ test('extract.DEFAULT', () => {
+ let config = {
+ content: {
+ files: [{ raw: sharedHtml }],
+ extract: {
+ DEFAULT: customExtractor,
},
- }
+ },
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(expected)
- })
- })
-
- oxide.test.todo('extract.{extension}')
- stable.test('extract.{extension}', () => {
- let config = {
- content: {
- files: [{ raw: sharedHtml }],
- extract: {
- html: customExtractor,
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(expected)
- })
- })
-
- oxide.test.todo('extract function')
- stable.test('extract function', () => {
- let config = {
- content: {
- files: [{ raw: sharedHtml }],
- extract: customExtractor,
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(expected)
- })
- })
-
- oxide.test.todo('raw content with extension')
- stable.test('raw content with extension', () => {
- let config = {
- content: {
- files: [
- {
- raw: sharedHtml,
- extension: 'html',
- },
- ],
- extract: {
- html: () => ['invisible'],
- },
- },
- corePlugins: { preflight: false },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .invisible {
- visibility: hidden;
- }
- `)
- })
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(expected)
})
})
- describe('legacy', () => {
- oxide.test.todo('defaultExtractor')
- stable.test('defaultExtractor', () => {
- let config = {
- content: {
- files: [{ raw: sharedHtml }],
- options: {
- defaultExtractor: customExtractor,
- },
+ test('extract.{extension}', () => {
+ let config = {
+ content: {
+ files: [{ raw: sharedHtml }],
+ extract: {
+ html: customExtractor,
},
- }
+ },
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(expected)
- })
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(expected)
})
+ })
- oxide.test.todo('extractors array')
- stable.test('extractors array', () => {
- let config = {
- content: {
- files: [{ raw: sharedHtml }],
- options: {
- extractors: [
- {
- extractor: customExtractor,
- extensions: ['html'],
- },
- ],
+ test('extract function', () => {
+ let config = {
+ content: {
+ files: [{ raw: sharedHtml }],
+ extract: customExtractor,
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(expected)
+ })
+ })
+
+ test('raw content with extension', () => {
+ let config = {
+ content: {
+ files: [
+ {
+ raw: sharedHtml,
+ extension: 'html',
},
+ ],
+ extract: {
+ html: () => ['invisible'],
},
- }
+ },
+ corePlugins: { preflight: false },
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(expected)
- })
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .invisible {
+ visibility: hidden;
+ }
+ `)
+ })
+ })
+})
+
+describe('legacy', () => {
+ test('defaultExtractor', () => {
+ let config = {
+ content: {
+ files: [{ raw: sharedHtml }],
+ options: {
+ defaultExtractor: customExtractor,
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(expected)
+ })
+ })
+
+ test('extractors array', () => {
+ let config = {
+ content: {
+ files: [{ raw: sharedHtml }],
+ options: {
+ extractors: [
+ {
+ extractor: customExtractor,
+ extensions: ['html'],
+ },
+ ],
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(expected)
})
})
})
diff --git a/tests/custom-plugins.test.js b/tests/custom-plugins.test.js
index d91843199..ac3547317 100644
--- a/tests/custom-plugins.test.js
+++ b/tests/custom-plugins.test.js
@@ -1,18 +1,1132 @@
import createPlugin from '../src/public/create-plugin'
-import { crosscheck, run, html, css, defaults } from './util/run'
+import { run, html, css, defaults } from './util/run'
-crosscheck(() => {
- test('plugins can create utilities with object syntax', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addUtilities }) {
+test('plugins can create utilities with object syntax', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function ({ addUtilities }) {
+ addUtilities({
+ '.custom-object-fill': {
+ 'object-fit': 'fill',
+ },
+ '.custom-object-contain': {
+ 'object-fit': 'contain',
+ },
+ '.custom-object-cover': {
+ 'object-fit': 'cover',
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .custom-object-fill {
+ object-fit: fill;
+ }
+ .custom-object-contain {
+ object-fit: contain;
+ }
+ .custom-object-cover {
+ object-fit: cover;
+ }
+ `)
+ })
+})
+
+test('plugins can create utilities with arrays of objects', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function ({ addUtilities }) {
+ addUtilities([
+ {
+ '.custom-object-fill': {
+ 'object-fit': 'fill',
+ },
+ '.custom-object-contain': {
+ 'object-fit': 'contain',
+ },
+ '.custom-object-cover': {
+ 'object-fit': 'cover',
+ },
+ },
+ ])
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .custom-object-fill {
+ object-fit: fill;
+ }
+ .custom-object-contain {
+ object-fit: contain;
+ }
+ .custom-object-cover {
+ object-fit: cover;
+ }
+ `)
+ })
+})
+
+test('plugins can create utilities with raw PostCSS nodes', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function ({ addUtilities, postcss }) {
+ addUtilities([
+ postcss.rule({ selector: '.custom-object-fill' }).append([
+ postcss.decl({
+ prop: 'object-fit',
+ value: 'fill',
+ }),
+ ]),
+ postcss.rule({ selector: '.custom-object-contain' }).append([
+ postcss.decl({
+ prop: 'object-fit',
+ value: 'contain',
+ }),
+ ]),
+ postcss.rule({ selector: '.custom-object-cover' }).append([
+ postcss.decl({
+ prop: 'object-fit',
+ value: 'cover',
+ }),
+ ]),
+ ])
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .custom-object-fill {
+ object-fit: fill;
+ }
+ .custom-object-contain {
+ object-fit: contain;
+ }
+ .custom-object-cover {
+ object-fit: cover;
+ }
+ `)
+ })
+})
+
+test('plugins can create utilities with mixed object styles and PostCSS nodes', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function ({ addUtilities, postcss }) {
+ addUtilities([
+ {
+ '.custom-object-fill': {
+ objectFit: 'fill',
+ },
+ },
+ postcss.rule({ selector: '.custom-object-contain' }).append([
+ postcss.decl({
+ prop: 'object-fit',
+ value: 'contain',
+ }),
+ ]),
+ postcss.rule({ selector: '.custom-object-cover' }).append([
+ postcss.decl({
+ prop: 'object-fit',
+ value: 'cover',
+ }),
+ ]),
+ ])
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .custom-object-fill {
+ object-fit: fill;
+ }
+ .custom-object-contain {
+ object-fit: contain;
+ }
+ .custom-object-cover {
+ object-fit: cover;
+ }
+ `)
+ })
+})
+
+test('plugins can create components with object syntax', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function ({ addComponents }) {
+ addComponents({
+ '.btn-blue': {
+ backgroundColor: 'blue',
+ color: 'white',
+ padding: '.5rem 1rem',
+ borderRadius: '.25rem',
+ },
+ '.btn-blue:hover': {
+ backgroundColor: 'darkblue',
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .btn-blue {
+ color: #fff;
+ background-color: #00f;
+ border-radius: 0.25rem;
+ padding: 0.5rem 1rem;
+ }
+ .btn-blue:hover {
+ background-color: #00008b;
+ }
+ `)
+ })
+})
+
+test('plugins can add base styles with object syntax', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function ({ addBase }) {
+ addBase({
+ img: {
+ maxWidth: '100%',
+ },
+ button: {
+ fontFamily: 'inherit',
+ },
+ })
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ return run('@tailwind base', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ img {
+ max-width: 100%;
+ }
+ button {
+ font-family: inherit;
+ }
+ ${defaults}
+ `)
+ })
+})
+
+test('plugins can add base styles with raw PostCSS nodes', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ 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',
+ }),
+ ]),
+ ])
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ return run('@tailwind base', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ img {
+ max-width: 100%;
+ }
+ button {
+ font-family: inherit;
+ }
+ ${defaults}
+ `)
+ })
+})
+
+test('plugins can create components with raw PostCSS nodes', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ 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',
+ }),
+ ]),
+ ])
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .btn-blue {
+ color: #fff;
+ background-color: #00f;
+ border-radius: 0.25rem;
+ padding: 0.5rem 1rem;
+ }
+ .btn-blue:hover {
+ background-color: #00008b;
+ }
+ `)
+ })
+})
+
+test('plugins can create components with mixed object styles and raw PostCSS nodes', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ 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',
+ },
+ },
+ ])
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .btn-blue {
+ color: #fff;
+ background-color: #00f;
+ border-radius: 0.25rem;
+ padding: 0.5rem 1rem;
+ }
+ .btn-blue:hover {
+ background-color: #00008b;
+ }
+ `)
+ })
+})
+
+test('plugins can create components with media queries with object syntax', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function ({ addComponents }) {
+ addComponents({
+ '.custom-container': {
+ width: '100%',
+ },
+ '@media (min-width: 100px)': {
+ '.custom-container': {
+ maxWidth: '100px',
+ },
+ },
+ '@media (min-width: 200px)': {
+ '.custom-container': {
+ maxWidth: '200px',
+ },
+ },
+ '@media (min-width: 300px)': {
+ '.custom-container': {
+ maxWidth: '300px',
+ },
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .custom-container {
+ width: 100%;
+ }
+ @media (min-width: 100px) {
+ .custom-container {
+ max-width: 100px;
+ }
+ }
+ @media (min-width: 200px) {
+ .custom-container {
+ max-width: 200px;
+ }
+ }
+ @media (min-width: 300px) {
+ .custom-container {
+ max-width: 300px;
+ }
+ }
+ `)
+ })
+})
+
+test('media queries can be defined multiple times using objects-in-array syntax', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
`,
+ },
+ ],
+ plugins: [
+ function ({ addComponents }) {
+ addComponents([
+ {
+ '.custom-container': {
+ width: '100%',
+ },
+ '@media (min-width: 100px)': {
+ '.custom-container': {
+ maxWidth: '100px',
+ },
+ },
+ },
+ {
+ '.btn': {
+ padding: '1rem .5rem',
+ display: 'block',
+ },
+ '@media (min-width: 100px)': {
+ '.btn': {
+ display: 'inline-block',
+ },
+ },
+ },
+ ])
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .custom-container {
+ width: 100%;
+ }
+ @media (min-width: 100px) {
+ .custom-container {
+ max-width: 100px;
+ }
+ }
+ .btn {
+ padding: 1rem 0.5rem;
+ display: block;
+ }
+ @media (min-width: 100px) {
+ .btn {
+ display: inline-block;
+ }
+ }
+ `)
+ })
+})
+
+test('plugins can create nested rules', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ plugins: [
+ 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',
+ },
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .btn-blue {
+ color: #fff;
+ background-color: #00f;
+ border-radius: 0.25rem;
+ padding: 0.5rem 1rem;
+ }
+ .btn-blue:hover {
+ background-color: #00008b;
+ }
+ @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', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ e, addUtilities }) {
+ addUtilities({
+ [`.${e('custom-top-1/4')}`]: {
+ top: '25%',
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .custom-top-1\/4 {
+ top: 25%;
+ }
+ `)
+ })
+})
+
+test('plugins can access the current config', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addComponents, config }) {
+ let containerClasses = [
+ {
+ '.custom-container': {
+ width: '100%',
+ },
+ },
+ ]
+
+ for (let maxWidth of Object.values(config('theme.customScreens'))) {
+ containerClasses.push({
+ [`@media (min-width: ${maxWidth})`]: {
+ '.custom-container': { maxWidth },
+ },
+ })
+ }
+
+ addComponents(containerClasses)
+ },
+ ],
+ theme: {
+ customScreens: {
+ sm: '576px',
+ md: '768px',
+ lg: '992px',
+ xl: '1200px',
+ },
+ },
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .custom-container {
+ width: 100%;
+ }
+ @media (min-width: 576px) {
+ .custom-container {
+ max-width: 576px;
+ }
+ }
+ @media (min-width: 768px) {
+ .custom-container {
+ max-width: 768px;
+ }
+ }
+ @media (min-width: 992px) {
+ .custom-container {
+ max-width: 992px;
+ }
+ }
+ @media (min-width: 1200px) {
+ .custom-container {
+ max-width: 1200px;
+ }
+ }
+ `)
+ })
+})
+
+test('plugins can check if corePlugins are enabled', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addUtilities, corePlugins }) {
+ addUtilities({
+ '.test': {
+ 'text-color': corePlugins('textColor') ? 'true' : 'false',
+ opacity: corePlugins('opacity') ? 'true' : 'false',
+ },
+ })
+ },
+ ],
+ corePlugins: { textColor: false },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .test {
+ text-color: false;
+ opacity: true;
+ }
+ `)
+ })
+})
+
+test('plugins can check if corePlugins are enabled when using array white-listing', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addUtilities, corePlugins }) {
+ addUtilities({
+ '.test': {
+ 'text-color': corePlugins('textColor') ? 'true' : 'false',
+ opacity: corePlugins('opacity') ? 'true' : 'false',
+ },
+ })
+ },
+ ],
+ corePlugins: ['textColor'],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .test {
+ text-color: true;
+ opacity: false;
+ }
+ `)
+ })
+})
+
+test('plugins can provide fallbacks to keys missing from the config', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addComponents, config }) {
+ addComponents({
+ '.btn': {
+ borderRadius: config('borderRadius.default', '.25rem'),
+ },
+ })
+ },
+ ],
+ theme: {
+ borderRadius: {
+ 1: '1px',
+ 2: '2px',
+ 4: '4px',
+ 8: '8px',
+ },
+ },
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .btn {
+ border-radius: 0.25rem;
+ }
+ `)
+ })
+})
+
+test('plugins can add multiple sets of utilities and components', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
`,
+ },
+ ],
+ plugins: [
+ function ({ addUtilities, addComponents }) {
+ addComponents({
+ '.card': {
+ padding: '1rem',
+ borderRadius: '.25rem',
+ },
+ })
+
+ addUtilities({
+ '.custom-skew-12deg': {
+ transform: 'skewY(-12deg)',
+ },
+ })
+
+ addComponents({
+ '.btn': {
+ padding: '1rem .5rem',
+ display: 'inline-block',
+ },
+ })
+
+ addUtilities({
+ '.custom-border-collapse': {
+ borderCollapse: 'collapse',
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components; @tailwind utilities;', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .card {
+ border-radius: 0.25rem;
+ padding: 1rem;
+ }
+ .btn {
+ padding: 1rem 0.5rem;
+ display: inline-block;
+ }
+ .custom-skew-12deg {
+ transform: skewY(-12deg);
+ }
+ .custom-border-collapse {
+ border-collapse: collapse;
+ }
+ `)
+ })
+})
+
+test('plugins respect prefix and important options by default when adding utilities', () => {
+ let config = {
+ prefix: 'tw-',
+ important: true,
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addUtilities }) {
+ addUtilities({
+ '.custom-rotate-90': {
+ transform: 'rotate(90deg)',
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .tw-custom-rotate-90 {
+ transform: rotate(90deg) !important;
+ }
+ `)
+ })
+})
+
+test('when important is a selector it is used to scope utilities instead of adding !important', () => {
+ let config = {
+ prefix: 'tw-',
+ important: '#app',
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addUtilities }) {
+ addUtilities({
+ '.custom-rotate-90': {
+ transform: 'rotate(90deg)',
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ #app .tw-custom-rotate-90 {
+ transform: rotate(90deg);
+ }
+ `)
+ })
+})
+
+test('when important is a selector it scopes all selectors in a rule, even though defining utilities like this is stupid', () => {
+ let config = {
+ important: '#app',
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addUtilities }) {
+ addUtilities({
+ '.custom-rotate-90, .custom-rotate-1\\/4': {
+ transform: 'rotate(90deg)',
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ #app .custom-rotate-90,
+ #app .custom-rotate-1\/4 {
+ transform: rotate(90deg);
+ }
+ `)
+ })
+})
+
+test('important utilities are not made double important when important option is used', () => {
+ let config = {
+ important: true,
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addUtilities }) {
+ addUtilities({
+ '.custom-rotate-90': {
+ transform: 'rotate(90deg) !important',
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .custom-rotate-90 {
+ transform: rotate(90deg) !important;
+ }
+ `)
+ })
+})
+
+test("component declarations respect the 'prefix' option by default", () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addComponents }) {
+ addComponents({
+ '.btn-blue': {
+ backgroundColor: 'blue',
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .tw-btn-blue {
+ background-color: #00f;
+ }
+ `)
+ })
+})
+
+test('all selectors in a rule are prefixed', () => {
+ let config = {
+ prefix: 'tw-',
+ content: [
+ {
+ raw: html`
+
`,
+ },
+ ],
+ plugins: [
+ function ({ addUtilities, addComponents }) {
+ addUtilities({
+ '.custom-rotate-90, .custom-rotate-1\\/4': {
+ transform: 'rotate(90deg)',
+ },
+ })
+ addComponents({
+ '.btn-blue, .btn-red': {
+ padding: '10px',
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .tw-btn-blue,
+ .tw-btn-red {
+ padding: 10px;
+ }
+ `)
+ })
+})
+
+test("component declarations can optionally ignore 'prefix' option", () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addComponents }) {
+ addComponents(
+ {
+ '.btn-blue': {
+ backgroundColor: 'blue',
+ },
+ },
+ { respectPrefix: false }
+ )
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .btn-blue {
+ background-color: #00f;
+ }
+ `)
+ })
+})
+
+test("component declarations are not affected by the 'important' option", () => {
+ let config = {
+ important: true,
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addComponents }) {
+ addComponents({
+ '.btn-blue': {
+ backgroundColor: 'blue',
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .btn-blue {
+ background-color: #00f;
+ }
+ `)
+ })
+})
+
+test("plugins can apply the user's chosen prefix to components manually", () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addComponents, prefix }) {
+ addComponents(
+ {
+ [prefix('.btn-blue')]: {
+ backgroundColor: 'blue',
+ },
+ },
+ { respectPrefix: false }
+ )
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .tw-btn-blue {
+ background-color: #00f;
+ }
+ `)
+ })
+})
+
+test('prefix can optionally be ignored for utilities', () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addUtilities }) {
+ addUtilities(
+ {
+ '.custom-rotate-90': {
+ transform: 'rotate(90deg)',
+ },
+ },
+ { respectPrefix: false }
+ )
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .custom-rotate-90 {
+ transform: rotate(90deg);
+ }
+ `)
+ })
+})
+
+test('important can optionally be ignored for utilities', () => {
+ let config = {
+ important: true,
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addUtilities }) {
+ addUtilities(
+ {
+ '.custom-rotate-90': {
+ transform: 'rotate(90deg)',
+ },
+ },
+ { respectImportant: false }
+ )
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .custom-rotate-90 {
+ transform: rotate(90deg);
+ }
+ `)
+ })
+})
+
+test('prefix will prefix all classes in a selector', () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`
` }],
+ plugins: [
+ function ({ addComponents, prefix }) {
+ addComponents(
+ {
+ [prefix('.btn-blue .w-1\\/4 > h1.text-xl + a .bar')]: {
+ backgroundColor: 'blue',
+ },
+ },
+ { respectPrefix: false }
+ )
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .tw-btn-blue .tw-w-1\/4 > h1.tw-text-xl + a .tw-bar {
+ background-color: #00f;
+ }
+ `)
+ })
+})
+
+test('plugins can be provided as an object with a handler function', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ {
+ handler({ addUtilities }) {
addUtilities({
'.custom-object-fill': {
'object-fit': 'fill',
@@ -25,1273 +1139,95 @@ crosscheck(() => {
},
})
},
- ],
- }
+ },
+ ],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-object-fill {
- object-fit: fill;
- }
- .custom-object-contain {
- object-fit: contain;
- }
- .custom-object-cover {
- object-fit: cover;
- }
- `)
- })
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .custom-object-fill {
+ object-fit: fill;
+ }
+ .custom-object-contain {
+ object-fit: contain;
+ }
+ .custom-object-cover {
+ object-fit: cover;
+ }
+ `)
})
+})
- test('plugins can create utilities with arrays of objects', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addUtilities }) {
- addUtilities([
- {
- '.custom-object-fill': {
- 'object-fit': 'fill',
- },
- '.custom-object-contain': {
- 'object-fit': 'contain',
- },
- '.custom-object-cover': {
- 'object-fit': 'cover',
- },
- },
- ])
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-object-fill {
- object-fit: fill;
- }
- .custom-object-contain {
- object-fit: contain;
- }
- .custom-object-cover {
- object-fit: cover;
- }
- `)
- })
- })
-
- test('plugins can create utilities with raw PostCSS nodes', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addUtilities, postcss }) {
- addUtilities([
- postcss.rule({ selector: '.custom-object-fill' }).append([
- postcss.decl({
- prop: 'object-fit',
- value: 'fill',
- }),
- ]),
- postcss.rule({ selector: '.custom-object-contain' }).append([
- postcss.decl({
- prop: 'object-fit',
- value: 'contain',
- }),
- ]),
- postcss.rule({ selector: '.custom-object-cover' }).append([
- postcss.decl({
- prop: 'object-fit',
- value: 'cover',
- }),
- ]),
- ])
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-object-fill {
- object-fit: fill;
- }
- .custom-object-contain {
- object-fit: contain;
- }
- .custom-object-cover {
- object-fit: cover;
- }
- `)
- })
- })
-
- test('plugins can create utilities with mixed object styles and PostCSS nodes', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addUtilities, postcss }) {
- addUtilities([
- {
- '.custom-object-fill': {
- objectFit: 'fill',
- },
- },
- postcss.rule({ selector: '.custom-object-contain' }).append([
- postcss.decl({
- prop: 'object-fit',
- value: 'contain',
- }),
- ]),
- postcss.rule({ selector: '.custom-object-cover' }).append([
- postcss.decl({
- prop: 'object-fit',
- value: 'cover',
- }),
- ]),
- ])
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-object-fill {
- object-fit: fill;
- }
- .custom-object-contain {
- object-fit: contain;
- }
- .custom-object-cover {
- object-fit: cover;
- }
- `)
- })
- })
-
- test('plugins can create components with object syntax', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addComponents }) {
- addComponents({
- '.btn-blue': {
- backgroundColor: 'blue',
- color: 'white',
- padding: '.5rem 1rem',
- borderRadius: '.25rem',
- },
- '.btn-blue:hover': {
- backgroundColor: 'darkblue',
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .btn-blue {
- color: #fff;
- background-color: #00f;
- border-radius: 0.25rem;
- padding: 0.5rem 1rem;
- }
- .btn-blue:hover {
- background-color: #00008b;
- }
- `)
- })
- })
-
- test('plugins can add base styles with object syntax', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addBase }) {
- addBase({
- img: {
- maxWidth: '100%',
- },
- button: {
- fontFamily: 'inherit',
- },
- })
- },
- ],
- corePlugins: { preflight: false },
- }
-
- return run('@tailwind base', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- img {
- max-width: 100%;
- }
- button {
- font-family: inherit;
- }
- ${defaults}
- `)
- })
- })
-
- test('plugins can add base styles with raw PostCSS nodes', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- 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',
- }),
- ]),
- ])
- },
- ],
- corePlugins: { preflight: false },
- }
-
- return run('@tailwind base', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- img {
- max-width: 100%;
- }
- button {
- font-family: inherit;
- }
- ${defaults}
- `)
- })
- })
-
- test('plugins can create components with raw PostCSS nodes', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- 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',
- }),
- ]),
- ])
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .btn-blue {
- color: #fff;
- background-color: #00f;
- border-radius: 0.25rem;
- padding: 0.5rem 1rem;
- }
- .btn-blue:hover {
- background-color: #00008b;
- }
- `)
- })
- })
-
- test('plugins can create components with mixed object styles and raw PostCSS nodes', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- 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',
- },
- },
- ])
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .btn-blue {
- color: #fff;
- background-color: #00f;
- border-radius: 0.25rem;
- padding: 0.5rem 1rem;
- }
- .btn-blue:hover {
- background-color: #00008b;
- }
- `)
- })
- })
-
- test('plugins can create components with media queries with object syntax', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addComponents }) {
- addComponents({
- '.custom-container': {
- width: '100%',
- },
- '@media (min-width: 100px)': {
- '.custom-container': {
- maxWidth: '100px',
- },
- },
- '@media (min-width: 200px)': {
- '.custom-container': {
- maxWidth: '200px',
- },
- },
- '@media (min-width: 300px)': {
- '.custom-container': {
- maxWidth: '300px',
- },
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-container {
- width: 100%;
- }
- @media (min-width: 100px) {
- .custom-container {
- max-width: 100px;
- }
- }
- @media (min-width: 200px) {
- .custom-container {
- max-width: 200px;
- }
- }
- @media (min-width: 300px) {
- .custom-container {
- max-width: 300px;
- }
- }
- `)
- })
- })
-
- test('media queries can be defined multiple times using objects-in-array syntax', () => {
- let config = {
- content: [
- {
- raw: html`
-
`,
- },
- ],
- plugins: [
- function ({ addComponents }) {
- addComponents([
- {
- '.custom-container': {
- width: '100%',
- },
- '@media (min-width: 100px)': {
- '.custom-container': {
- maxWidth: '100px',
- },
- },
- },
- {
- '.btn': {
- padding: '1rem .5rem',
- display: 'block',
- },
- '@media (min-width: 100px)': {
- '.btn': {
- display: 'inline-block',
- },
- },
- },
- ])
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-container {
- width: 100%;
- }
- @media (min-width: 100px) {
- .custom-container {
- max-width: 100px;
- }
- }
- .btn {
- padding: 1rem 0.5rem;
- display: block;
- }
- @media (min-width: 100px) {
- .btn {
- display: inline-block;
- }
- }
- `)
- })
- })
-
- test('plugins can create nested rules', () => {
- let config = {
- content: [{ raw: html`
` }],
- plugins: [
- 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',
- },
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .btn-blue {
- color: #fff;
- background-color: #00f;
- border-radius: 0.25rem;
- padding: 0.5rem 1rem;
- }
- .btn-blue:hover {
- background-color: #00008b;
- }
- @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', () => {
- let config = {
- content: [{ raw: html`
` }],
- plugins: [
- function ({ e, addUtilities }) {
- addUtilities({
- [`.${e('custom-top-1/4')}`]: {
- top: '25%',
- },
- })
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-top-1\/4 {
- top: 25%;
- }
- `)
- })
- })
-
- test('plugins can access the current config', () => {
- let config = {
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addComponents, config }) {
- let containerClasses = [
- {
- '.custom-container': {
- width: '100%',
- },
- },
- ]
-
- for (let maxWidth of Object.values(config('theme.customScreens'))) {
- containerClasses.push({
- [`@media (min-width: ${maxWidth})`]: {
- '.custom-container': { maxWidth },
- },
- })
- }
-
- addComponents(containerClasses)
- },
- ],
- theme: {
- customScreens: {
- sm: '576px',
- md: '768px',
- lg: '992px',
- xl: '1200px',
+test('plugins can provide a config but no handler', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ {
+ config: {
+ prefix: 'tw-',
},
},
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-container {
- width: 100%;
- }
- @media (min-width: 576px) {
- .custom-container {
- max-width: 576px;
- }
- }
- @media (min-width: 768px) {
- .custom-container {
- max-width: 768px;
- }
- }
- @media (min-width: 992px) {
- .custom-container {
- max-width: 992px;
- }
- }
- @media (min-width: 1200px) {
- .custom-container {
- max-width: 1200px;
- }
- }
- `)
- })
- })
-
- test('plugins can check if corePlugins are enabled', () => {
- let config = {
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addUtilities, corePlugins }) {
+ {
+ handler({ addUtilities }) {
addUtilities({
- '.test': {
- 'text-color': corePlugins('textColor') ? 'true' : 'false',
- opacity: corePlugins('opacity') ? 'true' : 'false',
+ '.custom-object-fill': {
+ 'object-fit': 'fill',
+ },
+ '.custom-object-contain': {
+ 'object-fit': 'contain',
+ },
+ '.custom-object-cover': {
+ 'object-fit': 'cover',
},
})
},
- ],
- corePlugins: { textColor: false },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .test {
- text-color: false;
- opacity: true;
- }
- `)
- })
- })
-
- test('plugins can check if corePlugins are enabled when using array white-listing', () => {
- let config = {
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addUtilities, corePlugins }) {
- addUtilities({
- '.test': {
- 'text-color': corePlugins('textColor') ? 'true' : 'false',
- opacity: corePlugins('opacity') ? 'true' : 'false',
- },
- })
- },
- ],
- corePlugins: ['textColor'],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .test {
- text-color: true;
- opacity: false;
- }
- `)
- })
- })
-
- test('plugins can provide fallbacks to keys missing from the config', () => {
- let config = {
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addComponents, config }) {
- addComponents({
- '.btn': {
- borderRadius: config('borderRadius.default', '.25rem'),
- },
- })
- },
- ],
- theme: {
- borderRadius: {
- 1: '1px',
- 2: '2px',
- 4: '4px',
- 8: '8px',
- },
},
- }
+ ],
+ }
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .btn {
- border-radius: 0.25rem;
- }
- `)
- })
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .tw-custom-object-fill {
+ object-fit: fill;
+ }
+ .tw-custom-object-contain {
+ object-fit: contain;
+ }
+ .tw-custom-object-cover {
+ object-fit: cover;
+ }
+ `)
})
+})
- test('plugins can add multiple sets of utilities and components', () => {
- let config = {
- content: [
- {
- raw: html`
-
`,
- },
- ],
- plugins: [
- function ({ addUtilities, addComponents }) {
- addComponents({
- '.card': {
- padding: '1rem',
- borderRadius: '.25rem',
- },
- })
-
- addUtilities({
- '.custom-skew-12deg': {
- transform: 'skewY(-12deg)',
- },
- })
-
- addComponents({
- '.btn': {
- padding: '1rem .5rem',
- display: 'inline-block',
- },
- })
-
- addUtilities({
- '.custom-border-collapse': {
- borderCollapse: 'collapse',
- },
- })
- },
- ],
- }
-
- return run('@tailwind components; @tailwind utilities;', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .card {
- border-radius: 0.25rem;
- padding: 1rem;
- }
- .btn {
- padding: 1rem 0.5rem;
- display: inline-block;
- }
- .custom-skew-12deg {
- transform: skewY(-12deg);
- }
- .custom-border-collapse {
- border-collapse: collapse;
- }
- `)
- })
- })
-
- test('plugins respect prefix and important options by default when adding utilities', () => {
- let config = {
- prefix: 'tw-',
- important: true,
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addUtilities }) {
- addUtilities({
- '.custom-rotate-90': {
- transform: 'rotate(90deg)',
- },
- })
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .tw-custom-rotate-90 {
- transform: rotate(90deg) !important;
- }
- `)
- })
- })
-
- test('when important is a selector it is used to scope utilities instead of adding !important', () => {
- let config = {
- prefix: 'tw-',
- important: '#app',
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addUtilities }) {
- addUtilities({
- '.custom-rotate-90': {
- transform: 'rotate(90deg)',
- },
- })
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- #app .tw-custom-rotate-90 {
- transform: rotate(90deg);
- }
- `)
- })
- })
-
- test('when important is a selector it scopes all selectors in a rule, even though defining utilities like this is stupid', () => {
- let config = {
- important: '#app',
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addUtilities }) {
- addUtilities({
- '.custom-rotate-90, .custom-rotate-1\\/4': {
- transform: 'rotate(90deg)',
- },
- })
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- #app .custom-rotate-90,
- #app .custom-rotate-1\/4 {
- transform: rotate(90deg);
- }
- `)
- })
- })
-
- test('important utilities are not made double important when important option is used', () => {
- let config = {
- important: true,
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addUtilities }) {
- addUtilities({
- '.custom-rotate-90': {
- transform: 'rotate(90deg) !important',
- },
- })
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-rotate-90 {
- transform: rotate(90deg) !important;
- }
- `)
- })
- })
-
- test("component declarations respect the 'prefix' option by default", () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addComponents }) {
- addComponents({
- '.btn-blue': {
- backgroundColor: 'blue',
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .tw-btn-blue {
- background-color: #00f;
- }
- `)
- })
- })
-
- test('all selectors in a rule are prefixed', () => {
- let config = {
- prefix: 'tw-',
- content: [
- {
- raw: html`
-
`,
- },
- ],
- plugins: [
- function ({ addUtilities, addComponents }) {
- addUtilities({
- '.custom-rotate-90, .custom-rotate-1\\/4': {
- transform: 'rotate(90deg)',
- },
- })
- addComponents({
- '.btn-blue, .btn-red': {
- padding: '10px',
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .tw-btn-blue,
- .tw-btn-red {
- padding: 10px;
- }
- `)
- })
- })
-
- test("component declarations can optionally ignore 'prefix' option", () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addComponents }) {
- addComponents(
- {
- '.btn-blue': {
- backgroundColor: 'blue',
- },
- },
- { respectPrefix: false }
- )
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .btn-blue {
- background-color: #00f;
- }
- `)
- })
- })
-
- test("component declarations are not affected by the 'important' option", () => {
- let config = {
- important: true,
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addComponents }) {
- addComponents({
- '.btn-blue': {
- backgroundColor: 'blue',
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .btn-blue {
- background-color: #00f;
- }
- `)
- })
- })
-
- test("plugins can apply the user's chosen prefix to components manually", () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addComponents, prefix }) {
- addComponents(
- {
- [prefix('.btn-blue')]: {
- backgroundColor: 'blue',
- },
- },
- { respectPrefix: false }
- )
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .tw-btn-blue {
- background-color: #00f;
- }
- `)
- })
- })
-
- test('prefix can optionally be ignored for utilities', () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addUtilities }) {
- addUtilities(
- {
- '.custom-rotate-90': {
- transform: 'rotate(90deg)',
- },
- },
- { respectPrefix: false }
- )
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-rotate-90 {
- transform: rotate(90deg);
- }
- `)
- })
- })
-
- test('important can optionally be ignored for utilities', () => {
- let config = {
- important: true,
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addUtilities }) {
- addUtilities(
- {
- '.custom-rotate-90': {
- transform: 'rotate(90deg)',
- },
- },
- { respectImportant: false }
- )
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-rotate-90 {
- transform: rotate(90deg);
- }
- `)
- })
- })
-
- test('prefix will prefix all classes in a selector', () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`
` }],
- plugins: [
- function ({ addComponents, prefix }) {
- addComponents(
- {
- [prefix('.btn-blue .w-1\\/4 > h1.text-xl + a .bar')]: {
- backgroundColor: 'blue',
- },
- },
- { respectPrefix: false }
- )
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .tw-btn-blue .tw-w-1\/4 > h1.tw-text-xl + a .tw-bar {
- background-color: #00f;
- }
- `)
- })
- })
-
- test('plugins can be provided as an object with a handler function', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- {
- handler({ addUtilities }) {
- addUtilities({
- '.custom-object-fill': {
- 'object-fit': 'fill',
- },
- '.custom-object-contain': {
- 'object-fit': 'contain',
- },
- '.custom-object-cover': {
- 'object-fit': 'cover',
- },
- })
- },
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .custom-object-fill {
- object-fit: fill;
- }
- .custom-object-contain {
- object-fit: contain;
- }
- .custom-object-cover {
- object-fit: cover;
- }
- `)
- })
- })
-
- test('plugins can provide a config but no handler', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- {
- config: {
- prefix: 'tw-',
- },
- },
- {
- handler({ addUtilities }) {
- addUtilities({
- '.custom-object-fill': {
- 'object-fit': 'fill',
- },
- '.custom-object-contain': {
- 'object-fit': 'contain',
- },
- '.custom-object-cover': {
- 'object-fit': 'cover',
- },
- })
- },
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .tw-custom-object-fill {
- object-fit: fill;
- }
- .tw-custom-object-contain {
- object-fit: contain;
- }
- .tw-custom-object-cover {
- object-fit: cover;
- }
- `)
- })
- })
-
- test('plugins can be created using the `createPlugin` function', () => {
- let config = {
- content: [
- { raw: html`
` },
- ],
- corePlugins: [],
- theme: {
- screens: {
- sm: '400px',
- },
+test('plugins can be created using the `createPlugin` function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ screens: {
+ sm: '400px',
},
- plugins: [
- createPlugin(
- function ({ addUtilities, theme }) {
- addUtilities(
- Object.fromEntries(
- Object.entries(theme('testPlugin')).map(([k, v]) => [
- `.test-${k}`,
- { testProperty: v },
- ])
- )
- )
- },
- {
- theme: {
- testPlugin: {
- sm: '1rem',
- md: '2rem',
- lg: '3rem',
- },
- },
- }
- ),
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .test-sm {
- test-property: 1rem;
- }
- .test-md {
- test-property: 2rem;
- }
- .test-lg {
- test-property: 3rem;
- }
- .hover\:test-sm:hover {
- test-property: 1rem;
- }
- @media (min-width: 400px) {
- .sm\:test-sm {
- test-property: 1rem;
- }
- }
- `)
- })
- })
-
- test('plugins with extra options can be created using the `createPlugin.withOptions` function', () => {
- let plugin = createPlugin.withOptions(
- function ({ className }) {
- return function ({ addUtilities, theme }) {
+ },
+ plugins: [
+ createPlugin(
+ function ({ addUtilities, theme }) {
addUtilities(
Object.fromEntries(
Object.entries(theme('testPlugin')).map(([k, v]) => [
- `.${className}-${k}`,
+ `.test-${k}`,
{ testProperty: v },
])
)
)
- }
- },
- function () {
- return {
+ },
+ {
theme: {
testPlugin: {
sm: '1rem',
@@ -1300,169 +1236,36 @@ crosscheck(() => {
},
},
}
+ ),
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .test-sm {
+ test-property: 1rem;
}
- )
-
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: [],
- theme: {
- screens: {
- sm: '400px',
- },
- },
- plugins: [plugin({ className: 'banana' })],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .banana-sm {
- test-property: 1rem;
- }
- .banana-md {
- test-property: 2rem;
- }
- .banana-lg {
- test-property: 3rem;
- }
- .hover\:banana-sm:hover {
- test-property: 1rem;
- }
- @media (min-width: 400px) {
- .sm\:banana-sm {
- test-property: 1rem;
- }
- }
- `)
- })
- })
-
- test('plugins should cache correctly', () => {
- let plugin = createPlugin.withOptions(
- ({ className = 'banana' } = {}) =>
- ({ addComponents }) => {
- addComponents({ [`.${className}`]: { position: 'absolute' } })
- }
- )
-
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- screens: {
- sm: '400px',
- },
- },
- }
-
- function internalRun(options = {}) {
- return run('@tailwind components', {
- ...config,
- plugins: [plugin(options)],
- })
- }
-
- return Promise.all([internalRun(), internalRun({ className: 'apple' })]).then(
- ([result1, result2]) => {
- let expected1 = css`
- .banana {
- position: absolute;
- }
- @media (min-width: 400px) {
- .sm\:banana {
- position: absolute;
- }
- }
- `
-
- let expected2 = css`
- .apple {
- position: absolute;
- }
- @media (min-width: 400px) {
- .sm\:apple {
- position: absolute;
- }
- }
- `
-
- expect(result1.css).toMatchCss(expected1)
- expect(result2.css).toMatchCss(expected2)
+ .test-md {
+ test-property: 2rem;
}
- )
- })
-
- test('plugins created using `createPlugin.withOptions` do not need to be invoked if the user wants to use the default options', () => {
- let plugin = createPlugin.withOptions(
- function ({ className } = { className: 'banana' }) {
- return function ({ addUtilities, theme }) {
- addUtilities(
- Object.fromEntries(
- Object.entries(theme('testPlugin')).map(([k, v]) => [
- `.${className}-${k}`,
- { testProperty: v },
- ])
- )
- )
- }
- },
- function () {
- return {
- theme: {
- testPlugin: {
- sm: '1rem',
- md: '2rem',
- lg: '3rem',
- },
- },
+ .test-lg {
+ test-property: 3rem;
+ }
+ .hover\:test-sm:hover {
+ test-property: 1rem;
+ }
+ @media (min-width: 400px) {
+ .sm\:test-sm {
+ test-property: 1rem;
}
}
- )
-
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: [],
- theme: {
- screens: {
- sm: '400px',
- },
- },
- plugins: [plugin],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .banana-sm {
- test-property: 1rem;
- }
- .banana-md {
- test-property: 2rem;
- }
- .banana-lg {
- test-property: 3rem;
- }
- .hover\:banana-sm:hover {
- test-property: 1rem;
- }
- @media (min-width: 400px) {
- .sm\:banana-sm {
- test-property: 1rem;
- }
- }
- `)
- })
+ `)
})
+})
- test('the configFunction parameter is optional when using the `createPlugin.withOptions` function', () => {
- let plugin = createPlugin.withOptions(function ({ className }) {
+test('plugins with extra options can be created using the `createPlugin.withOptions` function', () => {
+ let plugin = createPlugin.withOptions(
+ function ({ className }) {
return function ({ addUtilities, theme }) {
addUtilities(
Object.fromEntries(
@@ -1473,461 +1276,641 @@ crosscheck(() => {
)
)
}
- })
-
- let config = {
- content: [
- {
- raw: html`
`,
+ },
+ function () {
+ return {
+ theme: {
+ testPlugin: {
+ sm: '1rem',
+ md: '2rem',
+ lg: '3rem',
+ },
},
- ],
- corePlugins: [],
- theme: {
- screens: {
- sm: '400px',
- },
- testPlugin: {
- sm: '1px',
- md: '2px',
- lg: '3px',
- },
- },
- plugins: [plugin({ className: 'banana' })],
+ }
}
+ )
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .banana-sm {
- test-property: 1px;
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: [],
+ theme: {
+ screens: {
+ sm: '400px',
+ },
+ },
+ plugins: [plugin({ className: 'banana' })],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .banana-sm {
+ test-property: 1rem;
+ }
+ .banana-md {
+ test-property: 2rem;
+ }
+ .banana-lg {
+ test-property: 3rem;
+ }
+ .hover\:banana-sm:hover {
+ test-property: 1rem;
+ }
+ @media (min-width: 400px) {
+ .sm\:banana-sm {
+ test-property: 1rem;
}
- .banana-md {
- test-property: 2px;
- }
- .banana-lg {
- test-property: 3px;
- }
- .hover\:banana-sm:hover {
- test-property: 1px;
- }
- @media (min-width: 400px) {
- .sm\:banana-sm {
- test-property: 1px;
- }
- }
- `)
- })
+ }
+ `)
+ })
+})
+
+test('plugins should cache correctly', () => {
+ let plugin = createPlugin.withOptions(({ className = 'banana' } = {}) => ({ addComponents }) => {
+ addComponents({ [`.${className}`]: { position: 'absolute' } })
})
- test('keyframes are not escaped', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities({
- foo: (value) => {
- return {
- [`@keyframes ${value}`]: {
- '25.001%': {
- color: 'black',
- },
- },
- animation: `${value} 1s infinite`,
- }
- },
- })
- },
- ],
- }
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ screens: {
+ sm: '400px',
+ },
+ },
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @keyframes abc {
+ function internalRun(options = {}) {
+ return run('@tailwind components', {
+ ...config,
+ plugins: [plugin(options)],
+ })
+ }
+
+ return Promise.all([internalRun(), internalRun({ className: 'apple' })]).then(
+ ([result1, result2]) => {
+ let expected1 = css`
+ .banana {
+ position: absolute;
+ }
+ @media (min-width: 400px) {
+ .sm\:banana {
+ position: absolute;
+ }
+ }
+ `
+
+ let expected2 = css`
+ .apple {
+ position: absolute;
+ }
+ @media (min-width: 400px) {
+ .sm\:apple {
+ position: absolute;
+ }
+ }
+ `
+
+ expect(result1.css).toMatchFormattedCss(expected1)
+ expect(result2.css).toMatchFormattedCss(expected2)
+ }
+ )
+})
+
+test('plugins created using `createPlugin.withOptions` do not need to be invoked if the user wants to use the default options', () => {
+ let plugin = createPlugin.withOptions(
+ function ({ className } = { className: 'banana' }) {
+ return function ({ addUtilities, theme }) {
+ addUtilities(
+ Object.fromEntries(
+ Object.entries(theme('testPlugin')).map(([k, v]) => [
+ `.${className}-${k}`,
+ { testProperty: v },
+ ])
+ )
+ )
+ }
+ },
+ function () {
+ return {
+ theme: {
+ testPlugin: {
+ sm: '1rem',
+ md: '2rem',
+ lg: '3rem',
+ },
+ },
+ }
+ }
+ )
+
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: [],
+ theme: {
+ screens: {
+ sm: '400px',
+ },
+ },
+ plugins: [plugin],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .banana-sm {
+ test-property: 1rem;
+ }
+ .banana-md {
+ test-property: 2rem;
+ }
+ .banana-lg {
+ test-property: 3rem;
+ }
+ .hover\:banana-sm:hover {
+ test-property: 1rem;
+ }
+ @media (min-width: 400px) {
+ .sm\:banana-sm {
+ test-property: 1rem;
+ }
+ }
+ `)
+ })
+})
+
+test('the configFunction parameter is optional when using the `createPlugin.withOptions` function', () => {
+ let plugin = createPlugin.withOptions(function ({ className }) {
+ return function ({ addUtilities, theme }) {
+ addUtilities(
+ Object.fromEntries(
+ Object.entries(theme('testPlugin')).map(([k, v]) => [
+ `.${className}-${k}`,
+ { testProperty: v },
+ ])
+ )
+ )
+ }
+ })
+
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: [],
+ theme: {
+ screens: {
+ sm: '400px',
+ },
+ testPlugin: {
+ sm: '1px',
+ md: '2px',
+ lg: '3px',
+ },
+ },
+ plugins: [plugin({ className: 'banana' })],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .banana-sm {
+ test-property: 1px;
+ }
+ .banana-md {
+ test-property: 2px;
+ }
+ .banana-lg {
+ test-property: 3px;
+ }
+ .hover\:banana-sm:hover {
+ test-property: 1px;
+ }
+ @media (min-width: 400px) {
+ .sm\:banana-sm {
+ test-property: 1px;
+ }
+ }
+ `)
+ })
+})
+
+test('keyframes are not escaped', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities({
+ foo: (value) => {
+ return {
+ [`@keyframes ${value}`]: {
+ '25.001%': {
+ color: 'black',
+ },
+ },
+ animation: `${value} 1s infinite`,
+ }
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @keyframes abc {
+ 25.001% {
+ color: #000;
+ }
+ }
+ .foo-\[abc\] {
+ animation: 1s infinite abc;
+ }
+ @media (min-width: 768px) {
+ @keyframes def {
25.001% {
color: #000;
}
}
- .foo-\[abc\] {
- animation: 1s infinite abc;
+ .md\:foo-\[def\] {
+ animation: 1s infinite def;
}
- @media (min-width: 768px) {
- @keyframes def {
- 25.001% {
- color: #000;
- }
- }
- .md\:foo-\[def\] {
- animation: 1s infinite def;
- }
- }
- `)
- })
- })
-
- test('font sizes are retrieved without default line-heights or letter-spacing using theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- fontSize: {
- sm: ['14px', '20px'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- fontSize: theme('fontSize.sm'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- font-size: 14px;
- }
- `)
- })
- })
-
- test('outlines are retrieved without outline-offset using theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- outline: {
- black: ['2px dotted black', '4px'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- outline: theme('outline.black'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- outline: 2px dotted #000;
- }
- `)
- })
- })
-
- test('box-shadow values are joined when retrieved using the theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- boxShadow: {
- lol: ['width', 'height'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- boxShadow: theme('boxShadow.lol'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- box-shadow: width, height;
- }
- `)
- })
- })
-
- test('transition-property values are joined when retrieved using the theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- transitionProperty: {
- lol: ['width', 'height'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- transitionProperty: theme('transitionProperty.lol'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- transition-property: width, height;
- }
- `)
- })
- })
-
- test('transition-duration values are joined when retrieved using the theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- transitionDuration: {
- lol: ['width', 'height'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- transitionDuration: theme('transitionDuration.lol'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- transition-duration: width, height;
- }
- `)
- })
- })
-
- test('transition-delay values are joined when retrieved using the theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- transitionDuration: {
- lol: ['width', 'height'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- transitionDuration: theme('transitionDuration.lol'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- transition-duration: width, height;
- }
- `)
- })
- })
-
- test('transition-timing-function values are joined when retrieved using the theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- transitionTimingFunction: {
- lol: ['width', 'height'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- transitionTimingFunction: theme('transitionTimingFunction.lol'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- transition-timing-function: width, height;
- }
- `)
- })
- })
-
- test('background-image values are joined when retrieved using the theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- backgroundImage: {
- lol: ['width', 'height'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- backgroundImage: theme('backgroundImage.lol'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- background-image: width, height;
- }
- `)
- })
- })
-
- test('background-size values are joined when retrieved using the theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- backgroundSize: {
- lol: ['width', 'height'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- backgroundSize: theme('backgroundSize.lol'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- background-size: width, height;
- }
- `)
- })
- })
-
- test('background-color values are joined when retrieved using the theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- backgroundColor: {
- lol: ['width', 'height'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- backgroundColor: theme('backgroundColor.lol'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- background-color: width, height;
- }
- `)
- })
- })
-
- test('cursor values are joined when retrieved using the theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- cursor: {
- lol: ['width', 'height'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- cursor: theme('cursor.lol'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- cursor: width, height;
- }
- `)
- })
- })
-
- test('animation values are joined when retrieved using the theme function', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: [],
- theme: {
- animation: {
- lol: ['width', 'height'],
- },
- },
- plugins: [
- function ({ addComponents, theme }) {
- addComponents({
- '.foo': {
- animation: theme('animation.lol'),
- },
- })
- },
- ],
- }
-
- return run('@tailwind components', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- animation: width, height;
- }
- `)
- })
- })
-
- test('custom properties are not converted to kebab-case when added to base layer', () => {
- let config = {
- content: [],
- plugins: [
- function ({ addBase }) {
- addBase({
- ':root': {
- '--colors-primaryThing-500': '0, 0, 255',
- },
- })
- },
- ],
- }
-
- return run('@tailwind base', config).then((result) => {
- expect(result.css).toContain(`--colors-primaryThing-500: 0, 0, 255;`)
- })
+ }
+ `)
+ })
+})
+
+test('font sizes are retrieved without default line-heights or letter-spacing using theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ fontSize: {
+ sm: ['14px', '20px'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ fontSize: theme('fontSize.sm'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ font-size: 14px;
+ }
+ `)
+ })
+})
+
+test('outlines are retrieved without outline-offset using theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ outline: {
+ black: ['2px dotted black', '4px'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ outline: theme('outline.black'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ outline: 2px dotted #000;
+ }
+ `)
+ })
+})
+
+test('box-shadow values are joined when retrieved using the theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ boxShadow: {
+ lol: ['width', 'height'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ boxShadow: theme('boxShadow.lol'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ box-shadow: width, height;
+ }
+ `)
+ })
+})
+
+test('transition-property values are joined when retrieved using the theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ transitionProperty: {
+ lol: ['width', 'height'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ transitionProperty: theme('transitionProperty.lol'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ transition-property: width, height;
+ }
+ `)
+ })
+})
+
+test('transition-duration values are joined when retrieved using the theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ transitionDuration: {
+ lol: ['width', 'height'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ transitionDuration: theme('transitionDuration.lol'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ transition-duration: width, height;
+ }
+ `)
+ })
+})
+
+test('transition-delay values are joined when retrieved using the theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ transitionDuration: {
+ lol: ['width', 'height'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ transitionDuration: theme('transitionDuration.lol'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ transition-duration: width, height;
+ }
+ `)
+ })
+})
+
+test('transition-timing-function values are joined when retrieved using the theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ transitionTimingFunction: {
+ lol: ['width', 'height'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ transitionTimingFunction: theme('transitionTimingFunction.lol'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ transition-timing-function: width, height;
+ }
+ `)
+ })
+})
+
+test('background-image values are joined when retrieved using the theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ backgroundImage: {
+ lol: ['width', 'height'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ backgroundImage: theme('backgroundImage.lol'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ background-image: width, height;
+ }
+ `)
+ })
+})
+
+test('background-size values are joined when retrieved using the theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ backgroundSize: {
+ lol: ['width', 'height'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ backgroundSize: theme('backgroundSize.lol'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ background-size: width, height;
+ }
+ `)
+ })
+})
+
+test('background-color values are joined when retrieved using the theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ backgroundColor: {
+ lol: ['width', 'height'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ backgroundColor: theme('backgroundColor.lol'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ background-color: width, height;
+ }
+ `)
+ })
+})
+
+test('cursor values are joined when retrieved using the theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ cursor: {
+ lol: ['width', 'height'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ cursor: theme('cursor.lol'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ cursor: width, height;
+ }
+ `)
+ })
+})
+
+test('animation values are joined when retrieved using the theme function', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: [],
+ theme: {
+ animation: {
+ lol: ['width', 'height'],
+ },
+ },
+ plugins: [
+ function ({ addComponents, theme }) {
+ addComponents({
+ '.foo': {
+ animation: theme('animation.lol'),
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ animation: width, height;
+ }
+ `)
+ })
+})
+
+test('custom properties are not converted to kebab-case when added to base layer', () => {
+ let config = {
+ content: [],
+ plugins: [
+ function ({ addBase }) {
+ addBase({
+ ':root': {
+ '--colors-primaryThing-500': '0, 0, 255',
+ },
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind base', config).then((result) => {
+ expect(result.css).toContain(`--colors-primaryThing-500: 0, 0, 255;`)
})
})
diff --git a/tests/custom-separator.test.js b/tests/custom-separator.test.js
index adc1d55af..3b1708dfb 100644
--- a/tests/custom-separator.test.js
+++ b/tests/custom-separator.test.js
@@ -1,57 +1,55 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(() => {
- test('custom separator', () => {
- let config = {
- darkMode: 'class',
- content: [
- {
- raw: html`
-
-
-
-
-
- `,
- },
- ],
- separator: '_',
- }
+test('custom separator', () => {
+ let config = {
+ darkMode: 'class',
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+ `,
+ },
+ ],
+ separator: '_',
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .group:hover .group-hover_focus-within_text-left:focus-within {
- text-align: left;
- }
- :is([dir='rtl'] .rtl_active_text-center:active) {
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .group:hover .group-hover_focus-within_text-left:focus-within {
+ text-align: left;
+ }
+ :is([dir='rtl'] .rtl_active_text-center:active) {
+ text-align: center;
+ }
+ @media (prefers-reduced-motion: no-preference) {
+ .motion-safe_hover_text-center:hover {
text-align: center;
}
- @media (prefers-reduced-motion: no-preference) {
- .motion-safe_hover_text-center:hover {
- text-align: center;
- }
+ }
+ :is(.dark .dark_focus_text-left:focus) {
+ text-align: left;
+ }
+ @media (min-width: 768px) {
+ .md_hover_text-right:hover {
+ text-align: right;
}
- :is(.dark .dark_focus_text-left:focus) {
- text-align: left;
- }
- @media (min-width: 768px) {
- .md_hover_text-right:hover {
- text-align: right;
- }
- }
- `)
- })
- })
-
- test('dash is not supported', () => {
- let config = {
- darkMode: 'class',
- content: [{ raw: 'lg-hover-font-bold' }],
- separator: '-',
- }
-
- return expect(run('@tailwind utilities', config)).rejects.toThrowError(
- "The '-' character cannot be used as a custom separator in JIT mode due to parsing ambiguity. Please use another character like '_' instead."
- )
+ }
+ `)
})
})
+
+test('dash is not supported', () => {
+ let config = {
+ darkMode: 'class',
+ content: [{ raw: 'lg-hover-font-bold' }],
+ separator: '-',
+ }
+
+ return expect(run('@tailwind utilities', config)).rejects.toThrowError(
+ "The '-' character cannot be used as a custom separator in JIT mode due to parsing ambiguity. Please use another character like '_' instead."
+ )
+})
diff --git a/tests/custom-transformers.test.js b/tests/custom-transformers.test.js
index 14e3abac0..2715a7155 100644
--- a/tests/custom-transformers.test.js
+++ b/tests/custom-transformers.test.js
@@ -1,72 +1,67 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
function customTransformer(content) {
return content.replace(/uppercase/g, 'lowercase')
}
-crosscheck(({ stable, oxide }) => {
- oxide.test.todo('transform function')
- stable.test('transform function', () => {
- let config = {
- content: {
- files: [{ raw: html`
` }],
- transform: customTransformer,
- },
- }
+test('transform function', () => {
+ let config = {
+ content: {
+ files: [{ raw: html`
` }],
+ transform: customTransformer,
+ },
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .lowercase {
- text-transform: lowercase;
- }
- `)
- })
- })
-
- oxide.test.todo('transform.DEFAULT')
- stable.test('transform.DEFAULT', () => {
- let config = {
- content: {
- files: [{ raw: html`
` }],
- transform: {
- DEFAULT: customTransformer,
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .lowercase {
- text-transform: lowercase;
- }
- `)
- })
- })
-
- oxide.test.todo('transform.{extension}')
- stable.test('transform.{extension}', () => {
- let config = {
- content: {
- files: [
- { raw: 'blah blah blah', extension: 'html' },
- { raw: 'blah blah blah', extension: 'php' },
- ],
- transform: {
- html: () => 'uppercase',
- php: () => 'lowercase',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .uppercase {
- text-transform: uppercase;
- }
- .lowercase {
- text-transform: lowercase;
- }
- `)
- })
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .lowercase {
+ text-transform: lowercase;
+ }
+ `)
+ })
+})
+
+test('transform.DEFAULT', () => {
+ let config = {
+ content: {
+ files: [{ raw: html`
` }],
+ transform: {
+ DEFAULT: customTransformer,
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .lowercase {
+ text-transform: lowercase;
+ }
+ `)
+ })
+})
+
+test('transform.{extension}', () => {
+ let config = {
+ content: {
+ files: [
+ { raw: 'blah blah blah', extension: 'html' },
+ { raw: 'blah blah blah', extension: 'php' },
+ ],
+ transform: {
+ html: () => 'uppercase',
+ php: () => 'lowercase',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .uppercase {
+ text-transform: uppercase;
+ }
+ .lowercase {
+ text-transform: lowercase;
+ }
+ `)
})
})
diff --git a/tests/customConfig.test.js b/tests/customConfig.test.js
index 5b83f9f2e..b8f459144 100644
--- a/tests/customConfig.test.js
+++ b/tests/customConfig.test.js
@@ -2,37 +2,100 @@ import fs from 'fs'
import path from 'path'
import inTempDirectory from '../jest/runInTempDirectory'
-import { crosscheck, run, html, css, javascript } from './util/run'
+import { run, html, css, javascript } from './util/run'
const defaultConfigFile = 'tailwind.config.js'
const cjsConfigFile = 'tailwind.config.cjs'
-crosscheck(() => {
- test('it uses the values from the custom config file', () => {
- let config = require(path.resolve(`${__dirname}/fixtures/custom-config.js`))
+test('it uses the values from the custom config file', () => {
+ let config = require(path.resolve(`${__dirname}/fixtures/custom-config.js`))
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 400px) {
- .mobile\:font-bold {
- font-weight: 700;
- }
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 400px) {
+ .mobile\:font-bold {
+ font-weight: 700;
}
- `)
- })
+ }
+ `)
})
+})
- test('custom config can be passed as an object', () => {
- let config = {
+test('custom config can be passed as an object', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 400px) {
+ .mobile\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+ })
+})
+
+test('custom config path can be passed using `config` property in an object', () => {
+ let config = {
+ config: path.resolve(`${__dirname}/fixtures/custom-config.js`),
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 400px) {
+ .mobile\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+ })
+})
+
+test('custom config can be passed under the `config` property', () => {
+ let config = {
+ config: {
content: [{ raw: html`
` }],
theme: {
screens: {
mobile: '400px',
},
},
- }
+ },
+ }
- return run('@tailwind utilities', config).then((result) => {
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 400px) {
+ .mobile\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+ })
+})
+
+test('tailwind.config.cjs is picked up by default', () => {
+ return inTempDirectory(() => {
+ fs.writeFileSync(
+ path.resolve(cjsConfigFile),
+ javascript`module.exports = {
+ content: [{ raw: '
' }],
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
+ },
+ }`
+ )
+
+ return run('@tailwind utilities').then((result) => {
expect(result.css).toMatchFormattedCss(css`
@media (min-width: 400px) {
.mobile\:font-bold {
@@ -42,349 +105,284 @@ crosscheck(() => {
`)
})
})
-
- test('custom config path can be passed using `config` property in an object', () => {
- let config = {
- config: path.resolve(`${__dirname}/fixtures/custom-config.js`),
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 400px) {
- .mobile\:font-bold {
- font-weight: 700;
- }
- }
- `)
- })
- })
-
- test('custom config can be passed under the `config` property', () => {
- let config = {
- config: {
- content: [{ raw: html`
` }],
- theme: {
- screens: {
- mobile: '400px',
- },
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 400px) {
- .mobile\:font-bold {
- font-weight: 700;
- }
- }
- `)
- })
- })
-
- test('tailwind.config.cjs is picked up by default', () => {
- return inTempDirectory(() => {
- fs.writeFileSync(
- path.resolve(cjsConfigFile),
- javascript`module.exports = {
- content: [{ raw: '
' }],
- theme: {
- screens: {
- mobile: '400px',
- },
- },
- }`
- )
-
- return run('@tailwind utilities').then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 400px) {
- .mobile\:font-bold {
- font-weight: 700;
- }
- }
- `)
- })
- })
- })
-
- test('tailwind.config.js is picked up by default', () => {
- return inTempDirectory(() => {
- fs.writeFileSync(
- path.resolve(defaultConfigFile),
- javascript`module.exports = {
- content: [{ raw: '
' }],
- theme: {
- screens: {
- mobile: '400px',
- },
- },
- }`
- )
-
- return run('@tailwind utilities').then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 400px) {
- .mobile\:font-bold {
- font-weight: 700;
- }
- }
- `)
- })
- })
- })
-
- test('tailwind.config.cjs is picked up by default when passing an empty object', () => {
- return inTempDirectory(() => {
- fs.writeFileSync(
- path.resolve(cjsConfigFile),
- javascript`module.exports = {
- content: [{ raw: '
' }],
- theme: {
- screens: {
- mobile: '400px',
- },
- },
- }`
- )
-
- return run('@tailwind utilities', {}).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 400px) {
- .mobile\:font-bold {
- font-weight: 700;
- }
- }
- `)
- })
- })
- })
-
- test('tailwind.config.js is picked up by default when passing an empty object', () => {
- return inTempDirectory(() => {
- fs.writeFileSync(
- path.resolve(defaultConfigFile),
- javascript`module.exports = {
- content: [{ raw: '
' }],
- theme: {
- screens: {
- mobile: '400px',
- },
- },
- }`
- )
-
- return run('@tailwind utilities', {}).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 400px) {
- .mobile\:font-bold {
- font-weight: 700;
- }
- }
- `)
- })
- })
- })
-
- test('the default config can be overridden using the presets key', () => {
- let config = {
- content: [{ raw: html`
` }],
- presets: [
- {
- theme: {
- extend: { minHeight: { secondary: '24px' } },
- },
- },
- ],
- theme: {
- extend: { minHeight: { primary: '48px' } },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .min-h-0 {
- min-height: 0;
- }
- .min-h-primary {
- min-height: 48px;
- }
- .min-h-secondary {
- min-height: 24px;
- }
- `)
- })
- })
-
- test('presets can be functions', () => {
- let config = {
- content: [{ raw: html`
` }],
- presets: [
- () => ({
- theme: {
- extend: { minHeight: { secondary: '24px' } },
- },
- }),
- ],
- theme: {
- extend: { minHeight: { primary: '48px' } },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .min-h-0 {
- min-height: 0;
- }
- .min-h-primary {
- min-height: 48px;
- }
- .min-h-secondary {
- min-height: 24px;
- }
- `)
- })
- })
-
- test('the default config can be removed by using an empty presets key in a preset', () => {
- let config = {
- content: [{ raw: html`
` }],
- presets: [
- {
- presets: [],
- theme: {
- extend: { minHeight: { secondary: '24px' } },
- },
- },
- ],
- theme: {
- extend: { minHeight: { primary: '48px' } },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .min-h-primary {
- min-height: 48px;
- }
- .min-h-secondary {
- min-height: 24px;
- }
- `)
- })
- })
-
- test('presets can have their own presets', () => {
- let config = {
- content: [{ raw: html`
` }],
- presets: [
- {
- presets: [],
- theme: {
- colors: { red: '#dd0000' },
- },
- },
- {
- presets: [
- {
- presets: [],
- theme: {
- colors: {
- transparent: 'transparent',
- red: '#ff0000',
- },
- },
- },
- ],
- theme: {
- extend: {
- colors: {
- black: 'black',
- red: '#ee0000',
- },
- backgroundColor: (theme) => theme('colors'),
- },
- },
- corePlugins: ['backgroundColor'],
- },
- ],
- theme: {
- extend: { colors: { white: 'white' } },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .bg-black {
- background-color: #000;
- }
- .bg-red {
- background-color: #e00;
- }
- .bg-transparent {
- background-color: #0000;
- }
- .bg-white {
- background-color: #fff;
- }
- `)
- })
- })
-
- test('function presets can be mixed with object presets', () => {
- let config = {
- content: [{ raw: html`
` }],
- presets: [
- () => ({
- presets: [],
- theme: {
- colors: { red: '#dd0000' },
- },
- }),
- {
- presets: [
- () => ({
- presets: [],
- theme: {
- colors: {
- transparent: 'transparent',
- red: '#ff0000',
- },
- },
- }),
- ],
- theme: {
- extend: {
- colors: {
- black: 'black',
- red: '#ee0000',
- },
- backgroundColor: (theme) => theme('colors'),
- },
- },
- corePlugins: ['backgroundColor'],
- },
- ],
- theme: {
- extend: { colors: { white: 'white' } },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .bg-black {
- background-color: #000;
- }
- .bg-red {
- background-color: #e00;
- }
- .bg-transparent {
- background-color: #0000;
- }
- .bg-white {
- background-color: #fff;
- }
- `)
- })
- })
+})
+
+test('tailwind.config.js is picked up by default', () => {
+ return inTempDirectory(() => {
+ fs.writeFileSync(
+ path.resolve(defaultConfigFile),
+ javascript`module.exports = {
+ content: [{ raw: '
' }],
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
+ },
+ }`
+ )
+
+ return run('@tailwind utilities').then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 400px) {
+ .mobile\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+ })
+ })
+})
+
+test('tailwind.config.cjs is picked up by default when passing an empty object', () => {
+ return inTempDirectory(() => {
+ fs.writeFileSync(
+ path.resolve(cjsConfigFile),
+ javascript`module.exports = {
+ content: [{ raw: '
' }],
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
+ },
+ }`
+ )
+
+ return run('@tailwind utilities', {}).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 400px) {
+ .mobile\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+ })
+ })
+})
+
+test('tailwind.config.js is picked up by default when passing an empty object', () => {
+ return inTempDirectory(() => {
+ fs.writeFileSync(
+ path.resolve(defaultConfigFile),
+ javascript`module.exports = {
+ content: [{ raw: '
' }],
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
+ },
+ }`
+ )
+
+ return run('@tailwind utilities', {}).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 400px) {
+ .mobile\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+ })
+ })
+})
+
+test('the default config can be overridden using the presets key', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ presets: [
+ {
+ theme: {
+ extend: { minHeight: { secondary: '24px' } },
+ },
+ },
+ ],
+ theme: {
+ extend: { minHeight: { primary: '48px' } },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .min-h-0 {
+ min-height: 0;
+ }
+ .min-h-primary {
+ min-height: 48px;
+ }
+ .min-h-secondary {
+ min-height: 24px;
+ }
+ `)
+ })
+})
+
+test('presets can be functions', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ presets: [
+ () => ({
+ theme: {
+ extend: { minHeight: { secondary: '24px' } },
+ },
+ }),
+ ],
+ theme: {
+ extend: { minHeight: { primary: '48px' } },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .min-h-0 {
+ min-height: 0;
+ }
+ .min-h-primary {
+ min-height: 48px;
+ }
+ .min-h-secondary {
+ min-height: 24px;
+ }
+ `)
+ })
+})
+
+test('the default config can be removed by using an empty presets key in a preset', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ presets: [
+ {
+ presets: [],
+ theme: {
+ extend: { minHeight: { secondary: '24px' } },
+ },
+ },
+ ],
+ theme: {
+ extend: { minHeight: { primary: '48px' } },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .min-h-primary {
+ min-height: 48px;
+ }
+ .min-h-secondary {
+ min-height: 24px;
+ }
+ `)
+ })
+})
+
+test('presets can have their own presets', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ presets: [
+ {
+ presets: [],
+ theme: {
+ colors: { red: '#dd0000' },
+ },
+ },
+ {
+ presets: [
+ {
+ presets: [],
+ theme: {
+ colors: {
+ transparent: 'transparent',
+ red: '#ff0000',
+ },
+ },
+ },
+ ],
+ theme: {
+ extend: {
+ colors: {
+ black: 'black',
+ red: '#ee0000',
+ },
+ backgroundColor: (theme) => theme('colors'),
+ },
+ },
+ corePlugins: ['backgroundColor'],
+ },
+ ],
+ theme: {
+ extend: { colors: { white: 'white' } },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-black {
+ background-color: #000;
+ }
+ .bg-red {
+ background-color: #e00;
+ }
+ .bg-transparent {
+ background-color: #0000;
+ }
+ .bg-white {
+ background-color: #fff;
+ }
+ `)
+ })
+})
+
+test('function presets can be mixed with object presets', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ presets: [
+ () => ({
+ presets: [],
+ theme: {
+ colors: { red: '#dd0000' },
+ },
+ }),
+ {
+ presets: [
+ () => ({
+ presets: [],
+ theme: {
+ colors: {
+ transparent: 'transparent',
+ red: '#ff0000',
+ },
+ },
+ }),
+ ],
+ theme: {
+ extend: {
+ colors: {
+ black: 'black',
+ red: '#ee0000',
+ },
+ backgroundColor: (theme) => theme('colors'),
+ },
+ },
+ corePlugins: ['backgroundColor'],
+ },
+ ],
+ theme: {
+ extend: { colors: { white: 'white' } },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-black {
+ background-color: #000;
+ }
+ .bg-red {
+ background-color: #e00;
+ }
+ .bg-transparent {
+ background-color: #0000;
+ }
+ .bg-white {
+ background-color: #fff;
+ }
+ `)
+ })
})
diff --git a/tests/dark-mode.test.js b/tests/dark-mode.test.js
index ce06496b2..0c5273991 100644
--- a/tests/dark-mode.test.js
+++ b/tests/dark-mode.test.js
@@ -1,123 +1,121 @@
-import { crosscheck, run, html, css, defaults } from './util/run'
+import { run, html, css, defaults } from './util/run'
-crosscheck(() => {
- it('should be possible to use the darkMode "class" mode', () => {
- let config = {
- darkMode: 'class',
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
+it('should be possible to use the darkMode "class" mode', () => {
+ let config = {
+ darkMode: 'class',
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- :is(.dark .dark\:font-bold) {
- font-weight: 700;
- }
- `)
- })
- })
-
- it('should be possible to change the class name', () => {
- let config = {
- darkMode: ['class', '.test-dark'],
- 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}
- :is(.test-dark .dark\:font-bold) {
- font-weight: 700;
- }
- `)
- })
- })
-
- it('should be possible to use the darkMode "media" mode', () => {
- let config = {
- darkMode: 'media',
- 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) {
- .dark\:font-bold {
- font-weight: 700;
- }
- }
- `)
- })
- })
-
- it('should default to the `media` mode when no mode is provided', () => {
- 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) {
- .dark\:font-bold {
- font-weight: 700;
- }
- }
- `)
- })
- })
-
- it('should default to the `media` mode when mode is set to `false`', () => {
- let config = {
- darkMode: false,
- 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) {
- .dark\:font-bold {
- font-weight: 700;
- }
- }
- `)
- })
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults}
+ :is(.dark .dark\:font-bold) {
+ font-weight: 700;
+ }
+ `)
+ })
+})
+
+it('should be possible to change the class name', () => {
+ let config = {
+ darkMode: ['class', '.test-dark'],
+ 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}
+ :is(.test-dark .dark\:font-bold) {
+ font-weight: 700;
+ }
+ `)
+ })
+})
+
+it('should be possible to use the darkMode "media" mode', () => {
+ let config = {
+ darkMode: 'media',
+ 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) {
+ .dark\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+ })
+})
+
+it('should default to the `media` mode when no mode is provided', () => {
+ 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) {
+ .dark\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+ })
+})
+
+it('should default to the `media` mode when mode is set to `false`', () => {
+ let config = {
+ darkMode: false,
+ 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) {
+ .dark\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
})
})
diff --git a/tests/detect-nesting.test.js b/tests/detect-nesting.test.js
index 39af6d422..08eeacf98 100644
--- a/tests/detect-nesting.test.js
+++ b/tests/detect-nesting.test.js
@@ -1,130 +1,128 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(() => {
- it('should warn when we detect nested css', () => {
- let config = {
- content: [{ raw: html`
` }],
- }
+it('should warn when we detect nested css', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ }
- let input = css`
- @tailwind utilities;
+ let input = css`
+ @tailwind utilities;
- .nested {
- .example {
- }
+ .nested {
+ .example {
}
- `
-
- return run(input, config).then((result) => {
- expect(result.messages).toHaveLength(1)
- expect(result.messages).toMatchObject([
- {
- type: 'warning',
- text: [
- 'Nested CSS was detected, but CSS nesting has not been configured correctly.',
- 'Please enable a CSS nesting plugin *before* Tailwind in your configuration.',
- 'See how here: https://tailwindcss.com/docs/using-with-preprocessors#nesting',
- ].join('\n'),
- },
- ])
- })
- })
-
- it('should not warn when we detect nested css inside css @layer rules', () => {
- let config = {
- content: [{ raw: html`
` }],
}
+ `
- let input = css`
+ return run(input, config).then((result) => {
+ expect(result.messages).toHaveLength(1)
+ expect(result.messages).toMatchObject([
+ {
+ type: 'warning',
+ text: [
+ 'Nested CSS was detected, but CSS nesting has not been configured correctly.',
+ 'Please enable a CSS nesting plugin *before* Tailwind in your configuration.',
+ 'See how here: https://tailwindcss.com/docs/using-with-preprocessors#nesting',
+ ].join('\n'),
+ },
+ ])
+ })
+})
+
+it('should not warn when we detect nested css inside css @layer rules', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ }
+
+ let input = css`
+ @layer tw-base, tw-components, tw-utilities;
+ @layer tw-utilities {
+ @tailwind utilities;
+ }
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
@layer tw-base, tw-components, tw-utilities;
@layer tw-utilities {
- @tailwind utilities;
- }
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @layer tw-base, tw-components, tw-utilities;
- @layer tw-utilities {
- .underline {
- text-decoration-line: underline;
- }
- }
- `)
- expect(result.messages).toHaveLength(0)
- })
- })
-
- it('should warn when we detect namespaced @tailwind at rules', () => {
- let config = {
- content: [{ raw: html`
` }],
- }
-
- let input = css`
- .namespace {
- @tailwind utilities;
- }
- `
-
- return run(input, config).then((result) => {
- expect(result.messages).toHaveLength(1)
- expect(result.messages).toMatchObject([
- {
- type: 'warning',
- text: [
- 'Nested @tailwind rules were detected, but are not supported.',
- "Consider using a prefix to scope Tailwind's classes: https://tailwindcss.com/docs/configuration#prefix",
- 'Alternatively, use the important selector strategy: https://tailwindcss.com/docs/configuration#selector-strategy',
- ].join('\n'),
- },
- ])
- })
- })
-
- it('should not warn when nesting a single rule inside a media query', () => {
- let config = {
- content: [{ raw: html`
` }],
- }
-
- let input = css`
- @tailwind utilities;
-
- @media (min-width: 768px) {
- .nested {
+ .underline {
+ text-decoration-line: underline;
}
}
- `
-
- return run(input, config).then((result) => {
- expect(result.messages).toHaveLength(0)
- expect(result.messages).toEqual([])
- })
+ `)
+ expect(result.messages).toHaveLength(0)
})
+})
- it('should only warn for the first detected nesting ', () => {
- let config = {
- content: [{ raw: html`
` }],
- }
+it('should warn when we detect namespaced @tailwind at rules', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ }
- let input = css`
+ let input = css`
+ .namespace {
@tailwind utilities;
+ }
+ `
+ return run(input, config).then((result) => {
+ expect(result.messages).toHaveLength(1)
+ expect(result.messages).toMatchObject([
+ {
+ type: 'warning',
+ text: [
+ 'Nested @tailwind rules were detected, but are not supported.',
+ "Consider using a prefix to scope Tailwind's classes: https://tailwindcss.com/docs/configuration#prefix",
+ 'Alternatively, use the important selector strategy: https://tailwindcss.com/docs/configuration#selector-strategy',
+ ].join('\n'),
+ },
+ ])
+ })
+})
+
+it('should not warn when nesting a single rule inside a media query', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ }
+
+ let input = css`
+ @tailwind utilities;
+
+ @media (min-width: 768px) {
.nested {
- .example {
- }
+ }
+ }
+ `
- .other {
- }
+ return run(input, config).then((result) => {
+ expect(result.messages).toHaveLength(0)
+ expect(result.messages).toEqual([])
+ })
+})
+
+it('should only warn for the first detected nesting ', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ }
+
+ let input = css`
+ @tailwind utilities;
+
+ .nested {
+ .example {
}
.other {
- .example {
- }
}
- `
+ }
- return run(input, config).then((result) => {
- expect(result.messages).toHaveLength(1)
- })
+ .other {
+ .example {
+ }
+ }
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.messages).toHaveLength(1)
})
})
diff --git a/tests/escapeClassName.test.js b/tests/escapeClassName.test.js
index cdd834a79..7a8cd7b7b 100644
--- a/tests/escapeClassName.test.js
+++ b/tests/escapeClassName.test.js
@@ -1,8 +1,5 @@
import escapeClassName from '../src/util/escapeClassName'
-import { crosscheck } from './util/run'
-crosscheck(() => {
- test('invalid characters are escaped', () => {
- expect(escapeClassName('w:_$-1/2')).toEqual('w\\:_\\$-1\\/2')
- })
+test('invalid characters are escaped', () => {
+ expect(escapeClassName('w:_$-1/2')).toEqual('w\\:_\\$-1\\/2')
})
diff --git a/tests/evaluateTailwindFunctions.test.js b/tests/evaluateTailwindFunctions.test.js
index e4085e03e..e6bf124b6 100644
--- a/tests/evaluateTailwindFunctions.test.js
+++ b/tests/evaluateTailwindFunctions.test.js
@@ -2,7 +2,7 @@ import fs from 'fs'
import path from 'path'
import postcss from 'postcss'
import plugin from '../src/lib/evaluateTailwindFunctions'
-import { crosscheck, run as runFull, html, css } from './util/run'
+import { run as runFull, html, css } from './util/run'
function run(input, opts = {}) {
return postcss([
@@ -15,1373 +15,1377 @@ function run(input, opts = {}) {
]).process(input, { from: undefined })
}
-crosscheck(({ stable, oxide }) => {
- test('it looks up values in the theme using dot notation', () => {
- let input = css`
- .banana {
- color: theme('colors.yellow');
- }
- `
-
- let output = css`
- .banana {
- color: #f7cc50;
- }
- `
-
- return run(input, {
- theme: {
- colors: {
- yellow: '#f7cc50',
- },
- },
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
- })
- })
-
- test('it looks up values in the theme using bracket notation', () => {
- let input = css`
- .banana {
- color: theme('colors[yellow]');
- }
- `
-
- let output = css`
- .banana {
- color: #f7cc50;
- }
- `
-
- return run(input, {
- theme: {
- colors: {
- yellow: '#f7cc50',
- },
- },
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
- })
- })
-
- test('it looks up values in the theme using consecutive bracket notation', () => {
- let input = css`
- .banana {
- color: theme('colors[yellow][100]');
- }
- `
-
- let output = css`
- .banana {
- color: #f7cc50;
- }
- `
-
- return run(input, {
- theme: {
- colors: {
- yellow: {
- 100: '#f7cc50',
- },
- },
- },
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
- })
- })
-
- test('it looks up values in the theme using bracket notation that have dots in them', () => {
- let input = css`
- .banana {
- padding-top: theme('spacing[1.5]');
- }
- `
-
- let output = css`
- .banana {
- padding-top: 0.375rem;
- }
- `
-
- return run(input, {
- theme: {
- spacing: {
- '1.5': '0.375rem',
- },
- },
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
- })
- })
-
- test('theme with mismatched brackets throws an error ', async () => {
- let config = {
- theme: {
- spacing: {
- '1.5': '0.375rem',
- },
- },
+test('it looks up values in the theme using dot notation', () => {
+ let input = css`
+ .banana {
+ color: theme('colors.yellow');
}
+ `
- let input = (path) => css`
- .banana {
- padding-top: theme('${path}');
- }
- `
+ let output = css`
+ .banana {
+ color: #f7cc50;
+ }
+ `
- await expect(run(input('spacing[1.5]]'), config)).rejects.toThrowError(
- `Path is invalid. Has unbalanced brackets: spacing[1.5]]`
- )
-
- await expect(run(input('spacing[[1.5]'), config)).rejects.toThrowError(
- `Path is invalid. Has unbalanced brackets: spacing[[1.5]`
- )
-
- await expect(run(input('spacing[a['), config)).rejects.toThrowError(
- `Path is invalid. Has unbalanced brackets: spacing[a[`
- )
- })
-
- test('color can be a function', () => {
- let input = css`
- .backgroundColor {
- color: theme('backgroundColor.fn');
- }
- .borderColor {
- color: theme('borderColor.fn');
- }
- .caretColor {
- color: theme('caretColor.fn');
- }
- .colors {
- color: theme('colors.fn');
- }
- .divideColor {
- color: theme('divideColor.fn');
- }
- .fill {
- color: theme('fill.fn');
- }
- .gradientColorStops {
- color: theme('gradientColorStops.fn');
- }
- .placeholderColor {
- color: theme('placeholderColor.fn');
- }
- .ringColor {
- color: theme('ringColor.fn');
- }
- .ringOffsetColor {
- color: theme('ringOffsetColor.fn');
- }
- .stroke {
- color: theme('stroke.fn');
- }
- .textColor {
- color: theme('textColor.fn');
- }
- `
-
- let output = css`
- .backgroundColor {
- color: #f00;
- }
- .borderColor {
- color: #f00;
- }
- .caretColor {
- color: #f00;
- }
- .colors {
- color: #f00;
- }
- .divideColor {
- color: #f00;
- }
- .fill {
- color: #f00;
- }
- .gradientColorStops {
- color: #f00;
- }
- .placeholderColor {
- color: #f00;
- }
- .ringColor {
- color: #f00;
- }
- .ringOffsetColor {
- color: #f00;
- }
- .stroke {
- color: #f00;
- }
- .textColor {
- color: #f00;
- }
- `
-
- let fn = () => `#f00`
-
- return run(input, {
- theme: {
- backgroundColor: { fn },
- borderColor: { fn },
- caretColor: { fn },
- colors: { fn },
- divideColor: { fn },
- fill: { fn },
- gradientColorStops: { fn },
- placeholderColor: { fn },
- ringColor: { fn },
- ringOffsetColor: { fn },
- stroke: { fn },
- textColor: { fn },
+ return run(input, {
+ theme: {
+ colors: {
+ yellow: '#f7cc50',
},
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
- })
+ },
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('quotes are optional around the lookup path', () => {
- let input = css`
- .banana {
- color: theme(colors.yellow);
- }
- `
+test('it looks up values in the theme using bracket notation', () => {
+ let input = css`
+ .banana {
+ color: theme('colors[yellow]');
+ }
+ `
- let output = css`
- .banana {
- color: #f7cc50;
- }
- `
+ let output = css`
+ .banana {
+ color: #f7cc50;
+ }
+ `
- return run(input, {
+ return run(input, {
+ theme: {
+ colors: {
+ yellow: '#f7cc50',
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
+ })
+})
+
+test('it looks up values in the theme using consecutive bracket notation', () => {
+ let input = css`
+ .banana {
+ color: theme('colors[yellow][100]');
+ }
+ `
+
+ let output = css`
+ .banana {
+ color: #f7cc50;
+ }
+ `
+
+ return run(input, {
+ theme: {
+ colors: {
+ yellow: {
+ 100: '#f7cc50',
+ },
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
+ })
+})
+
+test('it looks up values in the theme using bracket notation that have dots in them', () => {
+ let input = css`
+ .banana {
+ padding-top: theme('spacing[1.5]');
+ }
+ `
+
+ let output = css`
+ .banana {
+ padding-top: 0.375rem;
+ }
+ `
+
+ return run(input, {
+ theme: {
+ spacing: {
+ '1.5': '0.375rem',
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
+ })
+})
+
+test('theme with mismatched brackets throws an error ', async () => {
+ let config = {
+ theme: {
+ spacing: {
+ '1.5': '0.375rem',
+ },
+ },
+ }
+
+ let input = (path) => css`
+ .banana {
+ padding-top: theme('${path}');
+ }
+ `
+
+ await expect(run(input('spacing[1.5]]'), config)).rejects.toThrowError(
+ `Path is invalid. Has unbalanced brackets: spacing[1.5]]`
+ )
+
+ await expect(run(input('spacing[[1.5]'), config)).rejects.toThrowError(
+ `Path is invalid. Has unbalanced brackets: spacing[[1.5]`
+ )
+
+ await expect(run(input('spacing[a['), config)).rejects.toThrowError(
+ `Path is invalid. Has unbalanced brackets: spacing[a[`
+ )
+})
+
+test('color can be a function', () => {
+ let input = css`
+ .backgroundColor {
+ color: theme('backgroundColor.fn');
+ }
+ .borderColor {
+ color: theme('borderColor.fn');
+ }
+ .caretColor {
+ color: theme('caretColor.fn');
+ }
+ .colors {
+ color: theme('colors.fn');
+ }
+ .divideColor {
+ color: theme('divideColor.fn');
+ }
+ .fill {
+ color: theme('fill.fn');
+ }
+ .gradientColorStops {
+ color: theme('gradientColorStops.fn');
+ }
+ .placeholderColor {
+ color: theme('placeholderColor.fn');
+ }
+ .ringColor {
+ color: theme('ringColor.fn');
+ }
+ .ringOffsetColor {
+ color: theme('ringOffsetColor.fn');
+ }
+ .stroke {
+ color: theme('stroke.fn');
+ }
+ .textColor {
+ color: theme('textColor.fn');
+ }
+ `
+
+ let output = css`
+ .backgroundColor {
+ color: #f00;
+ }
+ .borderColor {
+ color: #f00;
+ }
+ .caretColor {
+ color: #f00;
+ }
+ .colors {
+ color: #f00;
+ }
+ .divideColor {
+ color: #f00;
+ }
+ .fill {
+ color: #f00;
+ }
+ .gradientColorStops {
+ color: #f00;
+ }
+ .placeholderColor {
+ color: #f00;
+ }
+ .ringColor {
+ color: #f00;
+ }
+ .ringOffsetColor {
+ color: #f00;
+ }
+ .stroke {
+ color: #f00;
+ }
+ .textColor {
+ color: #f00;
+ }
+ `
+
+ let fn = () => `#f00`
+
+ return run(input, {
+ theme: {
+ backgroundColor: { fn },
+ borderColor: { fn },
+ caretColor: { fn },
+ colors: { fn },
+ divideColor: { fn },
+ fill: { fn },
+ gradientColorStops: { fn },
+ placeholderColor: { fn },
+ ringColor: { fn },
+ ringOffsetColor: { fn },
+ stroke: { fn },
+ textColor: { fn },
+ },
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
+ })
+})
+
+test('quotes are optional around the lookup path', () => {
+ let input = css`
+ .banana {
+ color: theme(colors.yellow);
+ }
+ `
+
+ let output = css`
+ .banana {
+ color: #f7cc50;
+ }
+ `
+
+ return run(input, {
+ theme: {
+ colors: {
+ yellow: '#f7cc50',
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
+ })
+})
+
+test('a default value can be provided', () => {
+ let input = css`
+ .cookieMonster {
+ color: theme('colors.blue', #0000ff);
+ }
+ `
+
+ let output = css`
+ .cookieMonster {
+ color: #0000ff;
+ }
+ `
+
+ return run(input, {
+ theme: {
+ colors: {
+ yellow: '#f7cc50',
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
+ })
+})
+
+test('the default value can use the theme function', () => {
+ let input = css`
+ .cookieMonster {
+ color: theme('colors.blue', theme('colors.yellow'));
+ }
+ `
+
+ let output = css`
+ .cookieMonster {
+ color: #f7cc50;
+ }
+ `
+
+ return run(input, {
+ theme: {
+ colors: {
+ yellow: '#f7cc50',
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
+ })
+})
+
+test('quotes are preserved around default values', () => {
+ let input = css`
+ .heading {
+ font-family: theme('fontFamily.sans', 'Helvetica Neue');
+ }
+ `
+
+ let output = css`
+ .heading {
+ font-family: 'Helvetica Neue';
+ }
+ `
+
+ return run(input, {
+ theme: {
+ fontFamily: {
+ serif: 'Constantia',
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
+ })
+})
+
+test('an unquoted list is valid as a default value', () => {
+ let input = css`
+ .heading {
+ font-family: theme('fontFamily.sans', Helvetica, Arial, sans-serif);
+ }
+ `
+
+ let output = css`
+ .heading {
+ font-family: Helvetica, Arial, sans-serif;
+ }
+ `
+
+ return run(input, {
+ theme: {
+ fontFamily: {
+ serif: 'Constantia',
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
+ })
+})
+
+test('a missing root theme value throws', () => {
+ let input = css`
+ .heading {
+ color: theme('colours.gray.100');
+ }
+ `
+
+ return expect(
+ run(input, {
theme: {
colors: {
yellow: '#f7cc50',
},
},
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
})
- })
+ ).rejects.toThrowError(
+ `'colours.gray.100' does not exist in your theme config. Your theme has the following top-level keys: 'colors'`
+ )
+})
- test('a default value can be provided', () => {
- let input = css`
- .cookieMonster {
- color: theme('colors.blue', #0000ff);
- }
- `
+test('a missing nested theme property throws', () => {
+ let input = css`
+ .heading {
+ color: theme('colors.red');
+ }
+ `
- let output = css`
- .cookieMonster {
- color: #0000ff;
- }
- `
+ return expect(
+ run(input, {
+ theme: {
+ colors: {
+ blue: 'blue',
+ yellow: '#f7cc50',
+ },
+ },
+ })
+ ).rejects.toThrowError(
+ `'colors.red' does not exist in your theme config. 'colors' has the following valid keys: 'blue', 'yellow'`
+ )
+})
- return run(input, {
+test('a missing nested theme property with a close alternative throws with a suggestion', () => {
+ let input = css`
+ .heading {
+ color: theme('colors.yellw');
+ }
+ `
+
+ return expect(
+ run(input, {
theme: {
colors: {
yellow: '#f7cc50',
},
},
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
})
- })
+ ).rejects.toThrowError(
+ `'colors.yellw' does not exist in your theme config. Did you mean 'colors.yellow'?`
+ )
+})
- test('the default value can use the theme function', () => {
- let input = css`
- .cookieMonster {
- color: theme('colors.blue', theme('colors.yellow'));
- }
- `
+test('a path through a non-object throws', () => {
+ let input = css`
+ .heading {
+ color: theme('colors.yellow.100');
+ }
+ `
- let output = css`
- .cookieMonster {
- color: #f7cc50;
- }
- `
-
- return run(input, {
+ return expect(
+ run(input, {
theme: {
colors: {
yellow: '#f7cc50',
},
},
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
})
- })
+ ).rejects.toThrowError(
+ `'colors.yellow.100' does not exist in your theme config. 'colors.yellow' is not an object.`
+ )
+})
- test('quotes are preserved around default values', () => {
- let input = css`
- .heading {
- font-family: theme('fontFamily.sans', 'Helvetica Neue');
- }
- `
+test('a path which exists but is not a string throws', () => {
+ let input = css`
+ .heading {
+ color: theme('colors.yellow');
+ }
+ `
- let output = css`
- .heading {
- font-family: 'Helvetica Neue';
- }
- `
-
- return run(input, {
+ return expect(
+ run(input, {
theme: {
- fontFamily: {
- serif: 'Constantia',
+ colors: {
+ yellow: Symbol(),
},
},
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
})
- })
+ ).rejects.toThrowError(`'colors.yellow' was found but does not resolve to a string.`)
+})
- test('an unquoted list is valid as a default value', () => {
- let input = css`
- .heading {
- font-family: theme('fontFamily.sans', Helvetica, Arial, sans-serif);
- }
- `
+test('a path which exists but is invalid throws', () => {
+ let input = css`
+ .heading {
+ color: theme('colors');
+ }
+ `
- let output = css`
- .heading {
- font-family: Helvetica, Arial, sans-serif;
- }
- `
-
- return run(input, {
+ return expect(
+ run(input, {
theme: {
- fontFamily: {
- serif: 'Constantia',
+ colors: {},
+ },
+ })
+ ).rejects.toThrowError(`'colors' was found but does not resolve to a string.`)
+})
+
+test('a path which is an object throws with a suggested key', () => {
+ let input = css`
+ .heading {
+ color: theme('colors');
+ }
+ `
+
+ return expect(
+ run(input, {
+ theme: {
+ colors: {
+ yellow: '#f7cc50',
},
},
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
})
- })
+ ).rejects.toThrowError(
+ `'colors' was found but does not resolve to a string. Did you mean something like 'colors.yellow'?`
+ )
+})
- test('a missing root theme value throws', () => {
- let input = css`
- .heading {
- color: theme('colours.gray.100');
- }
- `
+test('array values are joined by default', () => {
+ let input = css`
+ .heading {
+ font-family: theme('fontFamily.sans');
+ }
+ `
- return expect(
- run(input, {
- theme: {
- colors: {
- yellow: '#f7cc50',
- },
- },
- })
- ).rejects.toThrowError(
- `'colours.gray.100' does not exist in your theme config. Your theme has the following top-level keys: 'colors'`
- )
- })
+ let output = css`
+ .heading {
+ font-family: Inter, Helvetica, sans-serif;
+ }
+ `
- test('a missing nested theme property throws', () => {
- let input = css`
- .heading {
- color: theme('colors.red');
- }
- `
-
- return expect(
- run(input, {
- theme: {
- colors: {
- blue: 'blue',
- yellow: '#f7cc50',
- },
- },
- })
- ).rejects.toThrowError(
- `'colors.red' does not exist in your theme config. 'colors' has the following valid keys: 'blue', 'yellow'`
- )
- })
-
- test('a missing nested theme property with a close alternative throws with a suggestion', () => {
- let input = css`
- .heading {
- color: theme('colors.yellw');
- }
- `
-
- return expect(
- run(input, {
- theme: {
- colors: {
- yellow: '#f7cc50',
- },
- },
- })
- ).rejects.toThrowError(
- `'colors.yellw' does not exist in your theme config. Did you mean 'colors.yellow'?`
- )
- })
-
- test('a path through a non-object throws', () => {
- let input = css`
- .heading {
- color: theme('colors.yellow.100');
- }
- `
-
- return expect(
- run(input, {
- theme: {
- colors: {
- yellow: '#f7cc50',
- },
- },
- })
- ).rejects.toThrowError(
- `'colors.yellow.100' does not exist in your theme config. 'colors.yellow' is not an object.`
- )
- })
-
- test('a path which exists but is not a string throws', () => {
- let input = css`
- .heading {
- color: theme('colors.yellow');
- }
- `
-
- return expect(
- run(input, {
- theme: {
- colors: {
- yellow: Symbol(),
- },
- },
- })
- ).rejects.toThrowError(`'colors.yellow' was found but does not resolve to a string.`)
- })
-
- test('a path which exists but is invalid throws', () => {
- let input = css`
- .heading {
- color: theme('colors');
- }
- `
-
- return expect(
- run(input, {
- theme: {
- colors: {},
- },
- })
- ).rejects.toThrowError(`'colors' was found but does not resolve to a string.`)
- })
-
- test('a path which is an object throws with a suggested key', () => {
- let input = css`
- .heading {
- color: theme('colors');
- }
- `
-
- return expect(
- run(input, {
- theme: {
- colors: {
- yellow: '#f7cc50',
- },
- },
- })
- ).rejects.toThrowError(
- `'colors' was found but does not resolve to a string. Did you mean something like 'colors.yellow'?`
- )
- })
-
- test('array values are joined by default', () => {
- let input = css`
- .heading {
- font-family: theme('fontFamily.sans');
- }
- `
-
- let output = css`
- .heading {
- font-family: Inter, Helvetica, sans-serif;
- }
- `
-
- return run(input, {
- theme: {
- fontFamily: {
- sans: ['Inter', 'Helvetica', 'sans-serif'],
- },
+ return run(input, {
+ theme: {
+ fontFamily: {
+ sans: ['Inter', 'Helvetica', 'sans-serif'],
},
- }).then((result) => {
- expect(result.css).toEqual(output)
- expect(result.warnings().length).toBe(0)
- })
+ },
+ }).then((result) => {
+ expect(result.css).toEqual(output)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('font sizes are retrieved without default line-heights or letter-spacing', () => {
- let input = css`
- .heading-1 {
- font-size: theme('fontSize.lg');
- }
- .heading-2 {
- font-size: theme('fontSize.xl');
- }
- `
+test('font sizes are retrieved without default line-heights or letter-spacing', () => {
+ let input = css`
+ .heading-1 {
+ font-size: theme('fontSize.lg');
+ }
+ .heading-2 {
+ font-size: theme('fontSize.xl');
+ }
+ `
- let output = css`
- .heading-1 {
- font-size: 20px;
- }
- .heading-2 {
- font-size: 24px;
- }
- `
+ let output = css`
+ .heading-1 {
+ font-size: 20px;
+ }
+ .heading-2 {
+ font-size: 24px;
+ }
+ `
- return run(input, {
- theme: {
- fontSize: {
- lg: ['20px', '28px'],
- xl: ['24px', { lineHeight: '32px', letterSpacing: '-0.01em' }],
- },
+ return run(input, {
+ theme: {
+ fontSize: {
+ lg: ['20px', '28px'],
+ xl: ['24px', { lineHeight: '32px', letterSpacing: '-0.01em' }],
},
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('outlines are retrieved without default outline-offset', () => {
- let input = css`
+test('outlines are retrieved without default outline-offset', () => {
+ let input = css`
+ .element {
+ outline: theme('outline.black');
+ }
+ `
+
+ return run(input, {
+ theme: {
+ outline: {
+ black: ['2px dotted black', '4px'],
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
.element {
- outline: theme('outline.black');
+ outline: 2px dotted black;
}
- `
-
- let output = css`
- .element {
- outline: 2px dotted #000;
- }
- `
-
- return run(input, {
- theme: {
- outline: {
- black: ['2px dotted black', '4px'],
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+ `)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('font-family values are retrieved without font-variation-settings', () => {
- let input = css`
- .heading-1 {
- font-family: theme('fontFamily.sans');
- }
- .heading-2 {
- font-family: theme('fontFamily.serif');
- }
- .heading-3 {
- font-family: theme('fontFamily.mono');
- }
- `
+test('font-family values are retrieved without font-variation-settings', () => {
+ let input = css`
+ .heading-1 {
+ font-family: theme('fontFamily.sans');
+ }
+ .heading-2 {
+ font-family: theme('fontFamily.serif');
+ }
+ .heading-3 {
+ font-family: theme('fontFamily.mono');
+ }
+ `
- let output = css`
- .heading-1 {
- font-family: Inter;
- }
- .heading-2 {
- font-family: Times, serif;
- }
- .heading-3 {
- font-family: Menlo, monospace;
- }
- `
+ let output = css`
+ .heading-1 {
+ font-family: Inter;
+ }
+ .heading-2 {
+ font-family: Times, serif;
+ }
+ .heading-3 {
+ font-family: Menlo, monospace;
+ }
+ `
- return run(input, {
- theme: {
- fontFamily: {
- sans: ['Inter', { fontVariationSettings: '"opsz" 32' }],
- serif: [['Times', 'serif'], { fontVariationSettings: '"opsz" 32' }],
- mono: ['Menlo, monospace', { fontVariationSettings: '"opsz" 32' }],
- },
+ return run(input, {
+ theme: {
+ fontFamily: {
+ sans: ['Inter', { fontVariationSettings: '"opsz" 32' }],
+ serif: [['Times', 'serif'], { fontVariationSettings: '"opsz" 32' }],
+ mono: ['Menlo, monospace', { fontVariationSettings: '"opsz" 32' }],
},
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('font-variation-settings values can be retrieved', () => {
- let input = css`
- .heading {
- font-family: theme('fontFamily.sans');
- font-variation-settings: theme('fontFamily.sans[1].fontVariationSettings');
- }
- `
+test('font-variation-settings values can be retrieved', () => {
+ let input = css`
+ .heading {
+ font-family: theme('fontFamily.sans');
+ font-variation-settings: theme('fontFamily.sans[1].fontVariationSettings');
+ }
+ `
- let output = css`
+ let output = css`
+ .heading {
+ font-family: Inter;
+ font-variation-settings: 'opsz' 32;
+ }
+ `
+
+ return run(input, {
+ theme: {
+ fontFamily: {
+ sans: ['Inter', { fontVariationSettings: "'opsz' 32" }],
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+})
+
+test('font-family values are joined when an array', () => {
+ let input = css`
+ .element {
+ font-family: theme('fontFamily.sans');
+ }
+ `
+
+ let output = css`
+ .element {
+ font-family: Helvetica, Arial, sans-serif;
+ }
+ `
+
+ return run(input, {
+ theme: {
+ fontFamily: {
+ sans: ['Helvetica', 'Arial', 'sans-serif'],
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+})
+
+test('font-family values are retrieved without font-feature-settings', () => {
+ let input = css`
+ .heading-1 {
+ font-family: theme('fontFamily.sans');
+ }
+ .heading-2 {
+ font-family: theme('fontFamily.serif');
+ }
+ .heading-3 {
+ font-family: theme('fontFamily.mono');
+ }
+ `
+
+ let output = css`
+ .heading-1 {
+ font-family: Inter;
+ }
+ .heading-2 {
+ font-family: Times, serif;
+ }
+ .heading-3 {
+ font-family: Menlo, monospace;
+ }
+ `
+
+ return run(input, {
+ theme: {
+ fontFamily: {
+ sans: ['Inter', { fontFeatureSettings: '"cv11"' }],
+ serif: [['Times', 'serif'], { fontFeatureSettings: '"cv11"' }],
+ mono: ['Menlo, monospace', { fontFeatureSettings: '"cv11"' }],
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+})
+
+test('font-feature-settings values can be retrieved', () => {
+ let input = css`
+ .heading {
+ font-family: theme('fontFamily.sans');
+ font-feature-settings: theme('fontFamily.sans[1].fontFeatureSettings');
+ }
+ `
+
+ return run(input, {
+ theme: {
+ fontFamily: {
+ sans: ['Inter', { fontFeatureSettings: "'cv11'" }],
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
.heading {
font-family: Inter;
- font-variation-settings: 'opsz' 32;
- }
- `
-
- return run(input, {
- theme: {
- fontFamily: {
- sans: ['Inter', { fontVariationSettings: "'opsz' 32" }],
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
- })
-
- test('font-family values are joined when an array', () => {
- let input = css`
- .element {
- font-family: theme('fontFamily.sans');
- }
- `
-
- let output = css`
- .element {
- font-family: Helvetica, Arial, sans-serif;
- }
- `
-
- return run(input, {
- theme: {
- fontFamily: {
- sans: ['Helvetica', 'Arial', 'sans-serif'],
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
- })
-
- test('font-family values are retrieved without font-feature-settings', () => {
- let input = css`
- .heading-1 {
- font-family: theme('fontFamily.sans');
- }
- .heading-2 {
- font-family: theme('fontFamily.serif');
- }
- .heading-3 {
- font-family: theme('fontFamily.mono');
- }
- `
-
- let output = css`
- .heading-1 {
- font-family: Inter;
- }
- .heading-2 {
- font-family: Times, serif;
- }
- .heading-3 {
- font-family: Menlo, monospace;
- }
- `
-
- return run(input, {
- theme: {
- fontFamily: {
- sans: ['Inter', { fontFeatureSettings: '"cv11"' }],
- serif: [['Times', 'serif'], { fontFeatureSettings: '"cv11"' }],
- mono: ['Menlo, monospace', { fontFeatureSettings: '"cv11"' }],
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
- })
-
- test('font-feature-settings values can be retrieved', () => {
- let input = css`
- .heading {
- font-family: theme('fontFamily.sans');
- font-feature-settings: theme('fontFamily.sans[1].fontFeatureSettings');
- }
- `
-
- let output = css`
- .heading {
font-feature-settings: 'cv11';
- font-family: Inter;
}
- `
-
- return run(input, {
- theme: {
- fontFamily: {
- sans: ['Inter', { fontFeatureSettings: "'cv11'" }],
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+ `)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('box-shadow values are joined when an array', () => {
- let input = css`
- .element {
- box-shadow: theme('boxShadow.wtf');
- }
- `
+test('box-shadow values are joined when an array', () => {
+ let input = css`
+ .element {
+ box-shadow: theme('boxShadow.wtf');
+ }
+ `
- let output = css`
- .element {
- box-shadow: 0 0 2px #000, 1px 2px 3px #fff;
- }
- `
-
- return run(input, {
- theme: {
- boxShadow: {
- wtf: ['0 0 2px black', '1px 2px 3px white'],
- },
+ return run(input, {
+ theme: {
+ boxShadow: {
+ wtf: ['0 0 2px black', '1px 2px 3px white'],
},
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .element {
+ box-shadow: 0 0 2px black, 1px 2px 3px white;
+ }
+ `)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('transition-property values are joined when an array', () => {
- let input = css`
- .element {
- transition-property: theme('transitionProperty.colors');
- }
- `
+test('transition-property values are joined when an array', () => {
+ let input = css`
+ .element {
+ transition-property: theme('transitionProperty.colors');
+ }
+ `
- let output = css`
- .element {
- transition-property: color, fill;
- }
- `
+ let output = css`
+ .element {
+ transition-property: color, fill;
+ }
+ `
- return run(input, {
- theme: {
- transitionProperty: {
- colors: ['color', 'fill'],
- },
+ return run(input, {
+ theme: {
+ transitionProperty: {
+ colors: ['color', 'fill'],
},
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('transition-duration values are joined when an array', () => {
- let input = css`
- .element {
- transition-duration: theme('transitionDuration.lol');
- }
- `
+test('transition-duration values are joined when an array', () => {
+ let input = css`
+ .element {
+ transition-duration: theme('transitionDuration.lol');
+ }
+ `
- let output = css`
- .element {
- transition-duration: 1s, 2s;
- }
- `
+ let output = css`
+ .element {
+ transition-duration: 1s, 2s;
+ }
+ `
- return run(input, {
- theme: {
- transitionDuration: {
- lol: ['1s', '2s'],
- },
+ return run(input, {
+ theme: {
+ transitionDuration: {
+ lol: ['1s', '2s'],
},
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('basic screen function calls are expanded', () => {
- let input = css`
- @media screen(sm) {
- .foo {
- color: red;
- }
+test('basic screen function calls are expanded', () => {
+ let input = css`
+ @media screen(sm) {
+ .foo {
+ color: red;
}
- `
+ }
+ `
- let output = css`
+ return run(input, {
+ theme: { screens: { sm: '600px' } },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
@media (min-width: 600px) {
.foo {
color: red;
}
}
- `
-
- return run(input, {
- theme: { screens: { sm: '600px' } },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+ `)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('screen function supports max-width screens', () => {
- let input = css`
- @media screen(sm) {
- .foo {
- color: red;
- }
+test('screen function supports max-width screens', () => {
+ let input = css`
+ @media screen(sm) {
+ .foo {
+ color: red;
}
- `
+ }
+ `
- let output = css`
- @media (max-width: 600px) {
- .foo {
- color: red;
- }
+ let output = css`
+ @media (max-width: 600px) {
+ .foo {
+ color: red;
}
- `
+ }
+ `
- return run(input, {
- theme: { screens: { sm: { max: '600px' } } },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+ return run(input, {
+ theme: { screens: { sm: { max: '600px' } } },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('screen function supports min-width screens', () => {
- let input = css`
- @media screen(sm) {
- .foo {
- color: red;
- }
+test('screen function supports min-width screens', () => {
+ let input = css`
+ @media screen(sm) {
+ .foo {
+ color: red;
}
- `
+ }
+ `
- let output = css`
+ return run(input, {
+ theme: { screens: { sm: { min: '600px' } } },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
@media (min-width: 600px) {
.foo {
color: red;
}
}
- `
-
- return run(input, {
- theme: { screens: { sm: { min: '600px' } } },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+ `)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('screen function supports min-width and max-width screens', () => {
- let input = css`
- @media screen(sm) {
- .foo {
- color: red;
- }
+test('screen function supports min-width and max-width screens', () => {
+ let input = css`
+ @media screen(sm) {
+ .foo {
+ color: red;
}
- `
+ }
+ `
- let output = css`
+ return run(input, {
+ theme: { screens: { sm: { min: '600px', max: '700px' } } },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
@media (min-width: 600px) and (max-width: 700px) {
.foo {
color: red;
}
}
- `
-
- return run(input, {
- theme: { screens: { sm: { min: '600px', max: '700px' } } },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+ `)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('screen function supports raw screens', () => {
- let input = css`
- @media screen(mono) {
- .foo {
- color: red;
- }
+test('screen function supports raw screens', () => {
+ let input = css`
+ @media screen(mono) {
+ .foo {
+ color: red;
}
- `
+ }
+ `
- let output = css`
- @media monochrome {
- .foo {
- color: red;
- }
+ let output = css`
+ @media monochrome {
+ .foo {
+ color: red;
}
- `
+ }
+ `
- return run(input, {
- theme: { screens: { mono: { raw: 'monochrome' } } },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+ return run(input, {
+ theme: { screens: { mono: { raw: 'monochrome' } } },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('screen arguments can be quoted', () => {
- let input = css`
- @media screen('sm') {
- .foo {
- color: red;
- }
+test('screen arguments can be quoted', () => {
+ let input = css`
+ @media screen('sm') {
+ .foo {
+ color: red;
}
- `
+ }
+ `
- let output = css`
+ return run(input, {
+ theme: { screens: { sm: '600px' } },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
@media (min-width: 600px) {
.foo {
color: red;
}
}
- `
-
- return run(input, {
- theme: { screens: { sm: '600px' } },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+ `)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('Theme function can extract alpha values for colors (1)', () => {
- let input = css`
+test('Theme function can extract alpha values for colors (1)', () => {
+ let input = css`
+ .foo {
+ color: theme(colors.blue.500 / 50%);
+ }
+ `
+
+ return run(input, {
+ theme: {
+ colors: { blue: { 500: '#3b82f6' } },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
.foo {
- color: theme(colors.blue.500 / 50%);
+ color: rgb(59 130 246 / 50%);
}
- `
+ `)
+ expect(result.warnings().length).toBe(0)
+ })
+})
- let output = css`
+test('Theme function can extract alpha values for colors (2)', () => {
+ let input = css`
+ .foo {
+ color: theme(colors.blue.500 / 0.5);
+ }
+ `
+
+ return run(input, {
+ theme: {
+ colors: { blue: { 500: '#3b82f6' } },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
.foo {
- color: #3b82f680;
+ color: rgb(59 130 246 / 0.5);
}
- `
+ `)
+ expect(result.warnings().length).toBe(0)
+ })
+})
- return run(input, {
- theme: {
- colors: { blue: { 500: '#3b82f6' } },
+test('Theme function can extract alpha values for colors (3)', () => {
+ let input = css`
+ .foo {
+ color: theme(colors.blue.500 / var(--my-alpha));
+ }
+ `
+
+ let output = css`
+ .foo {
+ color: rgb(59 130 246 / var(--my-alpha));
+ }
+ `
+
+ return run(input, {
+ theme: {
+ colors: { blue: { 500: '#3b82f6' } },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+})
+
+test('Theme function can extract alpha values for colors (4)', () => {
+ let input = css`
+ .foo {
+ color: theme(colors.blue.500 / 50%);
+ }
+ `
+
+ return run(input, {
+ theme: {
+ colors: {
+ blue: { 500: 'hsl(217, 91%, 60%)' },
},
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ color: hsl(217 91% 60% / 50%);
+ }
+ `)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('Theme function can extract alpha values for colors (2)', () => {
- let input = css`
- .foo {
- color: theme(colors.blue.500 / 0.5);
- }
- `
+test('Theme function can extract alpha values for colors (5)', () => {
+ let input = css`
+ .foo {
+ color: theme(colors.blue.500 / 0.5);
+ }
+ `
- let output = css`
- .foo {
- color: #3b82f680;
- }
- `
-
- return run(input, {
- theme: {
- colors: { blue: { 500: '#3b82f6' } },
+ return run(input, {
+ theme: {
+ colors: {
+ blue: { 500: 'hsl(217, 91%, 60%)' },
},
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ color: hsl(217 91% 60% / 0.5);
+ }
+ `)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('Theme function can extract alpha values for colors (3)', () => {
- let input = css`
- .foo {
- color: theme(colors.blue.500 / var(--my-alpha));
- }
- `
+test('Theme function can extract alpha values for colors (6)', () => {
+ let input = css`
+ .foo {
+ color: theme(colors.blue.500 / var(--my-alpha));
+ }
+ `
- let output = css`
- .foo {
- color: rgb(59 130 246 / var(--my-alpha));
- }
- `
+ let output = css`
+ .foo {
+ color: hsl(217 91% 60% / var(--my-alpha));
+ }
+ `
- return run(input, {
- theme: {
- colors: { blue: { 500: '#3b82f6' } },
+ return run(input, {
+ theme: {
+ colors: {
+ blue: { 500: 'hsl(217, 91%, 60%)' },
},
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('Theme function can extract alpha values for colors (4)', () => {
- let input = css`
- .foo {
- color: theme(colors.blue.500 / 50%);
- }
- `
+test('Theme function can extract alpha values for colors (7)', () => {
+ let input = css`
+ .foo {
+ color: theme(colors.blue.500 / var(--my-alpha));
+ }
+ `
- let output = css`
- .foo {
- color: #3c83f680;
- }
- `
+ let output = css`
+ .foo {
+ color: rgb(var(--foo) / var(--my-alpha));
+ }
+ `
- return run(input, {
- theme: {
- colors: {
- blue: { 500: 'hsl(217, 91%, 60%)' },
+ return runFull(input, {
+ theme: {
+ colors: {
+ blue: {
+ 500: 'rgb(var(--foo) /
)',
},
},
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('Theme function can extract alpha values for colors (5)', () => {
- let input = css`
- .foo {
- color: theme(colors.blue.500 / 0.5);
- }
- `
+test('Theme function can extract alpha values for colors (8)', () => {
+ let input = css`
+ .foo {
+ color: theme(colors.blue.500 / theme(opacity.myalpha));
+ }
+ `
- let output = css`
- .foo {
- color: #3c83f680;
- }
- `
+ let output = css`
+ .foo {
+ color: rgb(var(--foo) / 50%);
+ }
+ `
- return run(input, {
- theme: {
- colors: {
- blue: { 500: 'hsl(217, 91%, 60%)' },
+ return runFull(input, {
+ theme: {
+ colors: {
+ blue: {
+ 500: 'rgb(var(--foo) / )',
},
},
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+
+ opacity: {
+ myalpha: '50%',
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('Theme function can extract alpha values for colors (6)', () => {
- let input = css`
- .foo {
- color: theme(colors.blue.500 / var(--my-alpha));
- }
- `
+test('Theme functions replace the alpha value placeholder even with no alpha provided', () => {
+ let input = css`
+ .foo {
+ background: theme(colors.blue.400);
+ color: theme(colors.blue.500);
+ }
+ `
- let output = css`
- .foo {
- color: hsl(217 91% 60% / var(--my-alpha));
- }
- `
+ let output = css`
+ .foo {
+ color: rgb(var(--foo) / 1);
+ background: #00f;
+ }
+ `
- return run(input, {
- theme: {
- colors: {
- blue: { 500: 'hsl(217, 91%, 60%)' },
+ return runFull(input, {
+ theme: {
+ colors: {
+ blue: {
+ 400: 'rgb(0 0 255 / )',
+ 500: 'rgb(var(--foo) / )',
},
},
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('Theme function can extract alpha values for colors (7)', () => {
- let input = css`
- .foo {
- color: theme(colors.blue.500 / var(--my-alpha));
- }
- `
+test('Theme functions can reference values with slashes in brackets', () => {
+ let input = css`
+ .foo1 {
+ color: theme(colors[a/b]);
+ }
+ .foo2 {
+ color: theme(colors[a/b]/50%);
+ }
+ `
- let output = css`
- .foo {
- color: rgb(var(--foo) / var(--my-alpha));
- }
- `
+ let output = css`
+ .foo1 {
+ color: #000;
+ }
+ .foo2 {
+ color: #00000080;
+ }
+ `
- return runFull(input, {
- theme: {
- colors: {
- blue: {
- 500: 'rgb(var(--foo) / )',
- },
- },
+ return runFull(input, {
+ theme: {
+ colors: {
+ 'a/b': '#000000',
},
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('Theme function can extract alpha values for colors (8)', () => {
- let input = css`
- .foo {
- color: theme(colors.blue.500 / theme(opacity.myalpha));
- }
- `
+test('Theme functions with alpha value inside quotes', () => {
+ let input = css`
+ .foo {
+ color: theme('colors.yellow / 50%');
+ }
+ `
- let output = css`
- .foo {
- color: rgb(var(--foo) / 50%);
- }
- `
+ let output = css`
+ .foo {
+ color: #f7cc5080;
+ }
+ `
- return runFull(input, {
- theme: {
- colors: {
- blue: {
- 500: 'rgb(var(--foo) / )',
- },
- },
-
- opacity: {
- myalpha: '50%',
- },
+ return runFull(input, {
+ theme: {
+ colors: {
+ yellow: '#f7cc50',
},
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('Theme functions replace the alpha value placeholder even with no alpha provided', () => {
- let input = css`
- .foo {
- background: theme(colors.blue.400);
- color: theme(colors.blue.500);
- }
- `
+test('Theme functions with alpha with quotes value around color only', () => {
+ let input = css`
+ .foo {
+ color: theme('colors.yellow' / 50%);
+ }
+ `
- let output = css`
- .foo {
- color: rgb(var(--foo) / 1);
- background: #00f;
- }
- `
+ let output = css`
+ .foo {
+ color: #f7cc5080;
+ }
+ `
- return runFull(input, {
- theme: {
- colors: {
- blue: {
- 400: 'rgb(0 0 255 / )',
- 500: 'rgb(var(--foo) / )',
- },
- },
+ return runFull(input, {
+ theme: {
+ colors: {
+ yellow: '#f7cc50',
},
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
})
+})
- test('Theme functions can reference values with slashes in brackets', () => {
- let input = css`
- .foo1 {
- color: theme(colors[a/b]);
- }
- .foo2 {
- color: theme(colors[a/b]/50%);
- }
- `
+it('can find values with slashes in the theme key while still allowing for alpha values ', () => {
+ let input = css`
+ .foo00 {
+ color: theme(colors.foo-5);
+ }
+ .foo01 {
+ color: theme(colors.foo-5/10);
+ }
+ .foo02 {
+ color: theme(colors.foo-5/10/25);
+ }
+ .foo03 {
+ color: theme(colors.foo-5 / 10);
+ }
+ .foo04 {
+ color: theme(colors.foo-5/10 / 25);
+ }
+ `
- let output = css`
- .foo1 {
- color: #000;
- }
- .foo2 {
- color: #00000080;
- }
- `
-
- return runFull(input, {
- theme: {
- colors: {
- 'a/b': '#000000',
- },
+ return runFull(input, {
+ theme: {
+ colors: {
+ 'foo-5': '#050000',
+ 'foo-5/10': '#051000',
+ 'foo-5/10/25': '#051025',
},
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
- })
-
- test('Theme functions with alpha value inside quotes', () => {
- let input = css`
- .foo {
- color: theme('colors.yellow / 50%');
- }
- `
-
- let output = css`
- .foo {
- color: #f7cc5080;
- }
- `
-
- return runFull(input, {
- theme: {
- colors: {
- yellow: '#f7cc50',
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
- })
-
- test('Theme functions with alpha with quotes value around color only', () => {
- let input = css`
- .foo {
- color: theme('colors.yellow' / 50%);
- }
- `
-
- let output = css`
- .foo {
- color: #f7cc5080;
- }
- `
-
- return runFull(input, {
- theme: {
- colors: {
- yellow: '#f7cc50',
- },
- },
- }).then((result) => {
- expect(result.css).toMatchCss(output)
- expect(result.warnings().length).toBe(0)
- })
- })
-
- it('can find values with slashes in the theme key while still allowing for alpha values ', () => {
- let input = css`
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
.foo00 {
- color: theme(colors.foo-5);
+ color: #050000;
}
.foo01 {
- color: theme(colors.foo-5/10);
+ color: #051000;
}
.foo02 {
- color: theme(colors.foo-5/10/25);
+ color: #051025;
}
.foo03 {
- color: theme(colors.foo-5 / 10);
+ color: #050000;
}
.foo04 {
- color: theme(colors.foo-5/10 / 25);
+ color: #051000;
+ }
+ `)
+ })
+})
+
+describe('context dependent', () => {
+ let configPath = path.resolve(__dirname, './evaluate-tailwind-functions.tailwind.config.js')
+ let filePath = path.resolve(__dirname, './evaluate-tailwind-functions.test.html')
+ let config = {
+ content: [filePath],
+ corePlugins: { preflight: false },
+ }
+
+ // Rebuild the config file for each test
+ beforeEach(() => fs.promises.writeFile(configPath, `module.exports = ${JSON.stringify(config)};`))
+ afterEach(() => fs.promises.unlink(configPath))
+
+ test('should not generate when theme fn doesnt resolve', async () => {
+ await fs.promises.writeFile(
+ filePath,
+ html`
+
+
+ `
+ )
+
+ // TODO: We need a way to reuse the context in our test suite without requiring writing to files
+ // It should be an explicit thing tho — like we create a context and pass it in or something
+ let result = await runFull('@tailwind utilities', configPath)
+
+ // 1. On first run it should work because it's been removed from the class cache
+ expect(result.css).toMatchFormattedCss(css`
+ .underline {
+ text-decoration-line: underline;
+ }
+ `)
+
+ // 2. But we get a warning in the console
+ expect().toHaveBeenWarnedWith(['invalid-theme-key-in-class'])
+
+ // 3. The second run should work fine because it's been removed from the class cache
+ result = await runFull('@tailwind utilities', configPath)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .underline {
+ text-decoration-line: underline;
+ }
+ `)
+
+ // 4. But we've not received any further logs about it
+ expect().toHaveBeenWarnedWith(['invalid-theme-key-in-class'])
+ })
+
+ test('it works mayhaps', async () => {
+ let input = css`
+ .test {
+ /* prettier-ignore */
+ inset: calc(-1 * (2*theme("spacing.4")));
+ /* prettier-ignore */
+ padding: calc(-1 * (2* theme("spacing.4")));
}
`
- return runFull(input, {
+ let output = css`
+ .test {
+ /* prettier-ignore */
+ inset: calc(-1 * (2*1rem));
+ /* prettier-ignore */
+ padding: calc(-1 * (2* 1rem));
+ }
+ `
+
+ return run(input, {
theme: {
- colors: {
- 'foo-5': '#050000',
- 'foo-5/10': '#051000',
- 'foo-5/10/25': '#051025',
+ spacing: {
+ 4: '1rem',
},
},
}).then((result) => {
- expect(result.css).toMatchCss(css`
- .foo00 {
- color: #050000;
- }
- .foo01 {
- color: #051000;
- }
- .foo02 {
- color: #051025;
- }
- .foo03 {
- color: #050000;
- }
- .foo04 {
- color: #051000;
- }
- `)
- })
- })
-
- describe('context dependent', () => {
- let configPath = path.resolve(__dirname, './evaluate-tailwind-functions.tailwind.config.js')
- let filePath = path.resolve(__dirname, './evaluate-tailwind-functions.test.html')
- let config = {
- content: [filePath],
- corePlugins: { preflight: false },
- }
-
- // Rebuild the config file for each test
- beforeEach(() =>
- fs.promises.writeFile(configPath, `module.exports = ${JSON.stringify(config)};`)
- )
- afterEach(() => fs.promises.unlink(configPath))
-
- oxide.test.todo('should not generate when theme fn doesnt resolve')
- stable.test('should not generate when theme fn doesnt resolve', async () => {
- await fs.promises.writeFile(
- filePath,
- html`
-
-
- `
- )
-
- // TODO: We need a way to reuse the context in our test suite without requiring writing to files
- // It should be an explicit thing tho — like we create a context and pass it in or something
- let result = await runFull('@tailwind utilities', configPath)
-
- // 1. On first run it should work because it's been removed from the class cache
- expect(result.css).toMatchCss(css`
- .underline {
- text-decoration-line: underline;
- }
- `)
-
- // 2. But we get a warning in the console
- expect().toHaveBeenWarnedWith(['invalid-theme-key-in-class'])
-
- // 3. The second run should work fine because it's been removed from the class cache
- result = await runFull('@tailwind utilities', configPath)
-
- expect(result.css).toMatchCss(css`
- .underline {
- text-decoration-line: underline;
- }
- `)
-
- // 4. But we've not received any further logs about it
- expect().toHaveBeenWarnedWith(['invalid-theme-key-in-class'])
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
})
})
})
diff --git a/tests/experimental.test.js b/tests/experimental.test.js
index 3497783f9..b36141cb0 100644
--- a/tests/experimental.test.js
+++ b/tests/experimental.test.js
@@ -1,197 +1,195 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(() => {
- test('experimental universal selector improvements (box-shadow)', () => {
- let config = {
- experimental: 'all',
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
+test('experimental universal selector improvements (box-shadow)', () => {
+ let config = {
+ experimental: 'all',
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchCss(css`
- .shadow {
- --tw-ring-offset-shadow: 0 0 #0000;
- --tw-ring-shadow: 0 0 #0000;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- }
- .resize {
- resize: both;
- }
- .shadow {
- --tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
- --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
- 0 1px 2px -1px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
- `)
- })
- })
-
- test('experimental universal selector improvements (pseudo hover)', () => {
- let config = {
- experimental: 'all',
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchCss(css`
- .hover\:shadow {
- --tw-ring-offset-shadow: 0 0 #0000;
- --tw-ring-shadow: 0 0 #0000;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- }
- .resize {
- resize: both;
- }
- .hover\:shadow:hover {
- --tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
- --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
- 0 1px 2px -1px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
- `)
- })
- })
-
- test('experimental universal selector improvements (multiple classes: group)', () => {
- let config = {
- experimental: 'all',
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchCss(css`
- .group-hover\:shadow {
- --tw-ring-offset-shadow: 0 0 #0000;
- --tw-ring-shadow: 0 0 #0000;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- }
- .resize {
- resize: both;
- }
- .group:hover .group-hover\:shadow {
- --tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
- --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
- 0 1px 2px -1px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
- `)
- })
- })
-
- test('experimental universal selector improvements (child selectors: divide-y)', () => {
- let config = {
- experimental: 'all',
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchCss(css`
- .resize {
- resize: both;
- }
- .divide-y > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-y-reverse: 0;
- border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
- border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
- }
- `)
- })
- })
-
- test('experimental universal selector improvements (hover:divide-y)', () => {
- let config = {
- experimental: 'all',
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchCss(css`
- .resize {
- resize: both;
- }
- .hover\:divide-y:hover > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-y-reverse: 0;
- border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
- border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
- }
- `)
- })
- })
-
- test('experimental universal selector improvements (#app important)', () => {
- let config = {
- experimental: 'all',
- important: '#app',
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchCss(css`
- .shadow {
- --tw-ring-offset-shadow: 0 0 #0000;
- --tw-ring-shadow: 0 0 #0000;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- }
- #app :is(.resize) {
- resize: both;
- }
- #app :is(.divide-y > :not([hidden]) ~ :not([hidden])) {
- --tw-divide-y-reverse: 0;
- border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
- border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
- }
- #app :is(.shadow) {
- --tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
- --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
- 0 1px 2px -1px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
- `)
- })
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .shadow {
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ }
+ .resize {
+ resize: both;
+ }
+ .shadow {
+ --tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
+ --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
+ 0 1px 2px -1px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
+ var(--tw-shadow);
+ }
+ `)
+ })
+})
+
+test('experimental universal selector improvements (pseudo hover)', () => {
+ let config = {
+ experimental: 'all',
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .hover\:shadow {
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ }
+ .resize {
+ resize: both;
+ }
+ .hover\:shadow:hover {
+ --tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
+ --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
+ 0 1px 2px -1px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
+ var(--tw-shadow);
+ }
+ `)
+ })
+})
+
+test('experimental universal selector improvements (multiple classes: group)', () => {
+ let config = {
+ experimental: 'all',
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .group-hover\:shadow {
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ }
+ .resize {
+ resize: both;
+ }
+ .group:hover .group-hover\:shadow {
+ --tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
+ --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
+ 0 1px 2px -1px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
+ var(--tw-shadow);
+ }
+ `)
+ })
+})
+
+test('experimental universal selector improvements (child selectors: divide-y)', () => {
+ let config = {
+ experimental: 'all',
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .resize {
+ resize: both;
+ }
+ .divide-y > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-y-reverse: 0;
+ border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
+ border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
+ }
+ `)
+ })
+})
+
+test('experimental universal selector improvements (hover:divide-y)', () => {
+ let config = {
+ experimental: 'all',
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .resize {
+ resize: both;
+ }
+ .hover\:divide-y:hover > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-y-reverse: 0;
+ border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
+ border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
+ }
+ `)
+ })
+})
+
+test('experimental universal selector improvements (#app important)', () => {
+ let config = {
+ experimental: 'all',
+ important: '#app',
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .shadow {
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ }
+ #app .resize {
+ resize: both;
+ }
+ #app :is(.divide-y > :not([hidden]) ~ :not([hidden])) {
+ --tw-divide-y-reverse: 0;
+ border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
+ border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
+ }
+ #app .shadow {
+ --tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
+ --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
+ 0 1px 2px -1px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
+ var(--tw-shadow);
+ }
+ `)
})
})
diff --git a/tests/extractor-edge-cases.test.js b/tests/extractor-edge-cases.test.js
index 412702b35..83ca99e37 100644
--- a/tests/extractor-edge-cases.test.js
+++ b/tests/extractor-edge-cases.test.js
@@ -1,34 +1,32 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(() => {
- test('PHP arrays', async () => {
- let config = {
- content: [
- {
- raw: html`">Hello world `,
- },
- ],
- }
+test('PHP arrays', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`">Hello world `,
+ },
+ ],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .max-w-\[16rem\] {
- max-width: 16rem;
- }
- `)
- })
- })
-
- test('arbitrary values with quotes', async () => {
- let config = { content: [{ raw: html`
` }] }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .content-\[\'hello\]\'\] {
- --tw-content: 'hello]';
- content: var(--tw-content);
- }
- `)
- })
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .max-w-\[16rem\] {
+ max-width: 16rem;
+ }
+ `)
+ })
+})
+
+test('arbitrary values with quotes', async () => {
+ let config = { content: [{ raw: html`
` }] }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .content-\[\'hello\]\'\] {
+ --tw-content: 'hello]';
+ content: var(--tw-content);
+ }
+ `)
})
})
diff --git a/tests/flattenColorPalette.test.js b/tests/flattenColorPalette.test.js
index dae66e806..e8edcd9aa 100644
--- a/tests/flattenColorPalette.test.js
+++ b/tests/flattenColorPalette.test.js
@@ -1,84 +1,81 @@
-import { crosscheck } from './util/run'
import flattenColorPalette from '../src/util/flattenColorPalette'
-crosscheck(() => {
- test('it flattens nested color objects', () => {
- expect(
- flattenColorPalette({
- purple: 'purple',
- white: {
- 25: 'rgba(255,255,255,.25)',
- 50: 'rgba(255,255,255,.5)',
- 75: 'rgba(255,255,255,.75)',
- DEFAULT: '#fff',
- },
- red: {
- 1: 'rgb(33,0,0)',
- 2: 'rgb(67,0,0)',
- 3: 'rgb(100,0,0)',
- },
- green: {
- 1: 'rgb(0,33,0)',
- 2: 'rgb(0,67,0)',
- 3: 'rgb(0,100,0)',
- },
- blue: {
- 1: 'rgb(0,0,33)',
- 2: 'rgb(0,0,67)',
- 3: 'rgb(0,0,100)',
- },
- })
- ).toEqual({
+test('it flattens nested color objects', () => {
+ expect(
+ flattenColorPalette({
purple: 'purple',
- 'white-25': 'rgba(255,255,255,.25)',
- 'white-50': 'rgba(255,255,255,.5)',
- 'white-75': 'rgba(255,255,255,.75)',
- white: '#fff',
- 'red-1': 'rgb(33,0,0)',
- 'red-2': 'rgb(67,0,0)',
- 'red-3': 'rgb(100,0,0)',
- 'green-1': 'rgb(0,33,0)',
- 'green-2': 'rgb(0,67,0)',
- 'green-3': 'rgb(0,100,0)',
- 'blue-1': 'rgb(0,0,33)',
- 'blue-2': 'rgb(0,0,67)',
- 'blue-3': 'rgb(0,0,100)',
+ white: {
+ 25: 'rgba(255,255,255,.25)',
+ 50: 'rgba(255,255,255,.5)',
+ 75: 'rgba(255,255,255,.75)',
+ DEFAULT: '#fff',
+ },
+ red: {
+ 1: 'rgb(33,0,0)',
+ 2: 'rgb(67,0,0)',
+ 3: 'rgb(100,0,0)',
+ },
+ green: {
+ 1: 'rgb(0,33,0)',
+ 2: 'rgb(0,67,0)',
+ 3: 'rgb(0,100,0)',
+ },
+ blue: {
+ 1: 'rgb(0,0,33)',
+ 2: 'rgb(0,0,67)',
+ 3: 'rgb(0,0,100)',
+ },
})
- })
-
- test('it flattens deeply nested color objects', () => {
- expect(
- flattenColorPalette({
- primary: 'purple',
- secondary: {
- DEFAULT: 'blue',
- hover: 'cyan',
- focus: 'red',
- },
- button: {
- primary: {
- DEFAULT: 'magenta',
- hover: 'green',
- focus: {
- DEFAULT: 'yellow',
- variant: 'orange',
- },
- },
- },
- })
- ).toEqual({
- primary: 'purple',
- secondary: 'blue',
- 'secondary-hover': 'cyan',
- 'secondary-focus': 'red',
- 'button-primary': 'magenta',
- 'button-primary-hover': 'green',
- 'button-primary-focus': 'yellow',
- 'button-primary-focus-variant': 'orange',
- })
- })
-
- test('it handles empty objects', () => {
- expect(flattenColorPalette({})).toEqual({})
+ ).toEqual({
+ purple: 'purple',
+ 'white-25': 'rgba(255,255,255,.25)',
+ 'white-50': 'rgba(255,255,255,.5)',
+ 'white-75': 'rgba(255,255,255,.75)',
+ white: '#fff',
+ 'red-1': 'rgb(33,0,0)',
+ 'red-2': 'rgb(67,0,0)',
+ 'red-3': 'rgb(100,0,0)',
+ 'green-1': 'rgb(0,33,0)',
+ 'green-2': 'rgb(0,67,0)',
+ 'green-3': 'rgb(0,100,0)',
+ 'blue-1': 'rgb(0,0,33)',
+ 'blue-2': 'rgb(0,0,67)',
+ 'blue-3': 'rgb(0,0,100)',
})
})
+
+test('it flattens deeply nested color objects', () => {
+ expect(
+ flattenColorPalette({
+ primary: 'purple',
+ secondary: {
+ DEFAULT: 'blue',
+ hover: 'cyan',
+ focus: 'red',
+ },
+ button: {
+ primary: {
+ DEFAULT: 'magenta',
+ hover: 'green',
+ focus: {
+ DEFAULT: 'yellow',
+ variant: 'orange',
+ },
+ },
+ },
+ })
+ ).toEqual({
+ primary: 'purple',
+ secondary: 'blue',
+ 'secondary-hover': 'cyan',
+ 'secondary-focus': 'red',
+ 'button-primary': 'magenta',
+ 'button-primary-hover': 'green',
+ 'button-primary-focus': 'yellow',
+ 'button-primary-focus-variant': 'orange',
+ })
+})
+
+test('it handles empty objects', () => {
+ expect(flattenColorPalette({})).toEqual({})
+})
diff --git a/tests/format-variant-selector.test.js b/tests/format-variant-selector.test.js
index 0cd0b05ff..3873f5d2d 100644
--- a/tests/format-variant-selector.test.js
+++ b/tests/format-variant-selector.test.js
@@ -1,363 +1,364 @@
import { finalizeSelector } from '../src/util/formatVariantSelector'
-import { crosscheck } from './util/run'
-crosscheck(() => {
- it('should be possible to add a simple variant to a simple selector', () => {
- let selector = '.text-center'
- let candidate = 'hover:text-center'
+it('should be possible to add a simple variant to a simple selector', () => {
+ let selector = '.text-center'
+ let candidate = 'hover:text-center'
+
+ let formats = [{ format: '&:hover', isArbitraryVariant: false }]
+
+ expect(finalizeSelector(selector, formats, { candidate })).toEqual('.hover\\:text-center:hover')
+})
+
+it('should be possible to add a multiple simple variants to a simple selector', () => {
+ let selector = '.text-center'
+ let candidate = 'focus:hover:text-center'
+
+ let formats = [
+ { format: '&:hover', isArbitraryVariant: false },
+ { format: '&:focus', isArbitraryVariant: false },
+ ]
+
+ expect(finalizeSelector(selector, formats, { candidate })).toEqual(
+ '.focus\\:hover\\:text-center:hover:focus'
+ )
+})
+
+it('should be possible to add a simple variant to a selector containing escaped parts', () => {
+ let selector = '.bg-\\[rgba\\(0\\,0\\,0\\)\\]'
+ let candidate = 'hover:bg-[rgba(0,0,0)]'
+
+ let formats = [{ format: '&:hover', isArbitraryVariant: false }]
+
+ expect(finalizeSelector(selector, formats, { candidate })).toEqual(
+ '.hover\\:bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]:hover'
+ )
+})
+
+it('should be possible to add a simple variant to a selector containing escaped parts (escape is slightly different)', () => {
+ let selector = '.bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]'
+ let candidate = 'hover:bg-[rgba(0,0,0)]'
+
+ let formats = [{ format: '&:hover', isArbitraryVariant: false }]
+
+ expect(finalizeSelector(selector, formats, { candidate })).toEqual(
+ '.hover\\:bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]:hover'
+ )
+})
+
+it('should be possible to add a simple variant to a more complex selector', () => {
+ let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
+ let candidate = 'hover:space-x-4'
+
+ let formats = [{ format: '&:hover', isArbitraryVariant: false }]
+
+ expect(finalizeSelector(selector, formats, { candidate })).toEqual(
+ '.hover\\:space-x-4:hover > :not([hidden]) ~ :not([hidden])'
+ )
+})
+
+it('should be possible to add multiple simple variants to a more complex selector', () => {
+ let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
+ let candidate = 'disabled:focus:hover:space-x-4'
+
+ let formats = [
+ { format: '&:hover', isArbitraryVariant: false },
+ { format: '&:focus', isArbitraryVariant: false },
+ { format: '&:disabled', isArbitraryVariant: false },
+ ]
+
+ expect(finalizeSelector(selector, formats, { candidate })).toEqual(
+ '.disabled\\:focus\\:hover\\:space-x-4:hover:focus:disabled > :not([hidden]) ~ :not([hidden])'
+ )
+})
+
+it('should be possible to add a single merge variant to a simple selector', () => {
+ let selector = '.text-center'
+ let candidate = 'group-hover:text-center'
+
+ let formats = [{ format: ':merge(.group):hover &', isArbitraryVariant: false }]
+
+ expect(finalizeSelector(selector, formats, { candidate })).toEqual(
+ '.group:hover .group-hover\\:text-center'
+ )
+})
+
+it('should be possible to add multiple merge variants to a simple selector', () => {
+ let selector = '.text-center'
+ let candidate = 'group-focus:group-hover:text-center'
+
+ let formats = [
+ { format: ':merge(.group):hover &', isArbitraryVariant: false },
+ { format: ':merge(.group):focus &', isArbitraryVariant: false },
+ ]
+
+ expect(finalizeSelector(selector, formats, { candidate })).toEqual(
+ '.group:focus:hover .group-focus\\:group-hover\\:text-center'
+ )
+})
+
+it('should be possible to add a single merge variant to a more complex selector', () => {
+ let selector = '.space-x-4 ~ :not([hidden]) ~ :not([hidden])'
+ let candidate = 'group-hover:space-x-4'
+
+ let formats = [{ format: ':merge(.group):hover &', isArbitraryVariant: false }]
+
+ expect(finalizeSelector(selector, formats, { candidate })).toEqual(
+ '.group:hover .group-hover\\:space-x-4 ~ :not([hidden]) ~ :not([hidden])'
+ )
+})
+
+it('should be possible to add multiple merge variants to a more complex selector', () => {
+ let selector = '.space-x-4 ~ :not([hidden]) ~ :not([hidden])'
+ let candidate = 'group-focus:group-hover:space-x-4'
+
+ let formats = [
+ { format: ':merge(.group):hover &', isArbitraryVariant: false },
+ { format: ':merge(.group):focus &', isArbitraryVariant: false },
+ ]
+
+ expect(finalizeSelector(selector, formats, { candidate })).toEqual(
+ '.group:focus:hover .group-focus\\:group-hover\\:space-x-4 ~ :not([hidden]) ~ :not([hidden])'
+ )
+})
+
+it('should be possible to add multiple unique merge variants to a simple selector', () => {
+ let selector = '.text-center'
+ let candidate = 'peer-focus:group-hover:text-center'
+
+ let formats = [
+ { format: ':merge(.group):hover &', isArbitraryVariant: false },
+ { format: ':merge(.peer):focus ~ &' },
+ ]
+
+ expect(finalizeSelector(selector, formats, { candidate })).toEqual(
+ '.peer:focus ~ .group:hover .peer-focus\\:group-hover\\:text-center'
+ )
+})
+
+it('should be possible to add multiple unique merge variants to a simple selector', () => {
+ let selector = '.text-center'
+ let candidate = 'group-hover:peer-focus:text-center'
+
+ let formats = [
+ { format: ':merge(.peer):focus ~ &', isArbitraryVariant: false },
+ { format: ':merge(.group):hover &', isArbitraryVariant: false },
+ ]
+
+ expect(finalizeSelector(selector, formats, { candidate })).toEqual(
+ '.group:hover .peer:focus ~ .group-hover\\:peer-focus\\:text-center'
+ )
+})
+
+it('should be possible to use multiple :merge() calls with different "arguments"', () => {
+ let selector = '.foo'
+ let candidate = 'peer-focus:group-focus:peer-hover:group-hover:foo'
+
+ let formats = [
+ { format: ':merge(.group):hover &', isArbitraryVariant: false },
+ { format: ':merge(.peer):hover ~ &', isArbitraryVariant: false },
+ { format: ':merge(.group):focus &', isArbitraryVariant: false },
+ { format: ':merge(.peer):focus ~ &', isArbitraryVariant: false },
+ ]
+
+ expect(finalizeSelector(selector, formats, { candidate })).toEqual(
+ '.peer:focus:hover ~ .group:focus:hover .peer-focus\\:group-focus\\:peer-hover\\:group-hover\\:foo'
+ )
+})
+
+it('group hover and prose headings combination', () => {
+ let selector = '.text-center'
+ let candidate = 'group-hover:prose-headings:text-center'
+ let formats = [
+ { format: ':where(&) :is(h1, h2, h3, h4)', isArbitraryVariant: false }, // Prose Headings
+ { format: ':merge(.group):hover &', isArbitraryVariant: false }, // Group Hover
+ ]
+
+ expect(finalizeSelector(selector, formats, { candidate })).toEqual(
+ '.group:hover :where(.group-hover\\:prose-headings\\:text-center) :is(h1, h2, h3, h4)'
+ )
+})
+
+it('group hover and prose headings combination flipped', () => {
+ let selector = '.text-center'
+ let candidate = 'prose-headings:group-hover:text-center'
+ let formats = [
+ { format: ':merge(.group):hover &', isArbitraryVariant: false }, // Group Hover
+ { format: ':where(&) :is(h1, h2, h3, h4)', isArbitraryVariant: false }, // Prose Headings
+ ]
+
+ expect(finalizeSelector(selector, formats, { candidate })).toEqual(
+ ':where(.group:hover .prose-headings\\:group-hover\\:text-center) :is(h1, h2, h3, h4)'
+ )
+})
+
+it('should be possible to handle a complex utility', () => {
+ let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
+ let candidate = 'peer-disabled:peer-first-child:group-hover:group-focus:focus:hover:space-x-4'
+ let formats = [
+ { format: '&:hover', isArbitraryVariant: false }, // Hover
+ { format: '&:focus', isArbitraryVariant: false }, // Focus
+ { format: ':merge(.group):focus &', isArbitraryVariant: false }, // Group focus
+ { format: ':merge(.group):hover &', isArbitraryVariant: false }, // Group hover
+ { format: ':merge(.peer):first-child ~ &', isArbitraryVariant: false }, // Peer first-child
+ { format: ':merge(.peer):disabled ~ &', isArbitraryVariant: false }, // Peer disabled
+ ]
+
+ expect(finalizeSelector(selector, formats, { candidate })).toEqual(
+ '.peer:disabled:first-child ~ .group:hover:focus .peer-disabled\\:peer-first-child\\:group-hover\\:group-focus\\:focus\\:hover\\:space-x-4:hover:focus > :not([hidden]) ~ :not([hidden])'
+ )
+})
+
+it('should match base utilities that are prefixed', () => {
+ let context = { tailwindConfig: { prefix: 'tw-' } }
+ let selector = '.tw-text-center'
+ let candidate = 'tw-text-center'
+ let formats = []
+
+ expect(finalizeSelector(selector, formats, { candidate, context })).toEqual('.tw-text-center')
+})
+
+it('should prefix classes from variants', () => {
+ let context = { tailwindConfig: { prefix: 'tw-' } }
+ let selector = '.tw-text-center'
+ let candidate = 'foo:tw-text-center'
+ let formats = [{ format: '.foo &', isArbitraryVariant: false }]
+
+ expect(finalizeSelector(selector, formats, { candidate, context })).toEqual(
+ '.tw-foo .foo\\:tw-text-center'
+ )
+})
+
+it('should not prefix classes from arbitrary variants', () => {
+ let context = { tailwindConfig: { prefix: 'tw-' } }
+ let selector = '.tw-text-center'
+ let candidate = '[.foo_&]:tw-text-center'
+ let formats = [{ format: '.foo &', isArbitraryVariant: true }]
+
+ expect(finalizeSelector(selector, formats, { candidate, context })).toEqual(
+ '.foo .\\[\\.foo_\\&\\]\\:tw-text-center'
+ )
+})
+
+it('Merged selectors with mixed combinators uses the first one', () => {
+ // This isn't explicitly specced behavior but it is how it works today
+
+ let selector = '.text-center'
+ let candidate = 'text-center'
+ let formats = [
+ { format: ':merge(.group):focus > &', isArbitraryVariant: true },
+ { format: ':merge(.group):hover &', isArbitraryVariant: true },
+ ]
+
+ expect(finalizeSelector(selector, formats, { candidate })).toEqual(
+ '.group:hover:focus > .text-center'
+ )
+})
+
+describe('real examples', () => {
+ it('example a', () => {
+ let selector = '.placeholder-red-500::placeholder'
+ let candidate = 'hover:placeholder-red-500'
let formats = [{ format: '&:hover', isArbitraryVariant: false }]
- expect(finalizeSelector(selector, formats, { candidate })).toEqual('.hover\\:text-center:hover')
+ expect(finalizeSelector(selector, formats, { candidate })).toEqual(
+ '.hover\\:placeholder-red-500:hover::placeholder'
+ )
})
- it('should be possible to add a multiple simple variants to a simple selector', () => {
- let selector = '.text-center'
- let candidate = 'focus:hover:text-center'
+ it('example b', () => {
+ let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
+ let candidate = 'group-hover:hover:space-x-4'
let formats = [
{ format: '&:hover', isArbitraryVariant: false },
- { format: '&:focus', isArbitraryVariant: false },
- ]
-
- expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- '.focus\\:hover\\:text-center:hover:focus'
- )
- })
-
- it('should be possible to add a simple variant to a selector containing escaped parts', () => {
- let selector = '.bg-\\[rgba\\(0\\,0\\,0\\)\\]'
- let candidate = 'hover:bg-[rgba(0,0,0)]'
-
- let formats = [{ format: '&:hover', isArbitraryVariant: false }]
-
- expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- '.hover\\:bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]:hover'
- )
- })
-
- it('should be possible to add a simple variant to a selector containing escaped parts (escape is slightly different)', () => {
- let selector = '.bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]'
- let candidate = 'hover:bg-[rgba(0,0,0)]'
-
- let formats = [{ format: '&:hover', isArbitraryVariant: false }]
-
- expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- '.hover\\:bg-\\[rgba\\(0\\2c 0\\2c 0\\)\\]:hover'
- )
- })
-
- it('should be possible to add a simple variant to a more complex selector', () => {
- let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
- let candidate = 'hover:space-x-4'
-
- let formats = [{ format: '&:hover', isArbitraryVariant: false }]
-
- expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- '.hover\\:space-x-4:hover > :not([hidden]) ~ :not([hidden])'
- )
- })
-
- it('should be possible to add multiple simple variants to a more complex selector', () => {
- let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
- let candidate = 'disabled:focus:hover:space-x-4'
-
- let formats = [
- { format: '&:hover', isArbitraryVariant: false },
- { format: '&:focus', isArbitraryVariant: false },
- { format: '&:disabled', isArbitraryVariant: false },
- ]
-
- expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- '.disabled\\:focus\\:hover\\:space-x-4:hover:focus:disabled > :not([hidden]) ~ :not([hidden])'
- )
- })
-
- it('should be possible to add a single merge variant to a simple selector', () => {
- let selector = '.text-center'
- let candidate = 'group-hover:text-center'
-
- let formats = [{ format: ':merge(.group):hover &', isArbitraryVariant: false }]
-
- expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- '.group:hover .group-hover\\:text-center'
- )
- })
-
- it('should be possible to add multiple merge variants to a simple selector', () => {
- let selector = '.text-center'
- let candidate = 'group-focus:group-hover:text-center'
-
- let formats = [
- { format: ':merge(.group):hover &', isArbitraryVariant: false },
- { format: ':merge(.group):focus &', isArbitraryVariant: false },
- ]
-
- expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- '.group:focus:hover .group-focus\\:group-hover\\:text-center'
- )
- })
-
- it('should be possible to add a single merge variant to a more complex selector', () => {
- let selector = '.space-x-4 ~ :not([hidden]) ~ :not([hidden])'
- let candidate = 'group-hover:space-x-4'
-
- let formats = [{ format: ':merge(.group):hover &', isArbitraryVariant: false }]
-
- expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- '.group:hover .group-hover\\:space-x-4 ~ :not([hidden]) ~ :not([hidden])'
- )
- })
-
- it('should be possible to add multiple merge variants to a more complex selector', () => {
- let selector = '.space-x-4 ~ :not([hidden]) ~ :not([hidden])'
- let candidate = 'group-focus:group-hover:space-x-4'
-
- let formats = [
- { format: ':merge(.group):hover &', isArbitraryVariant: false },
- { format: ':merge(.group):focus &', isArbitraryVariant: false },
- ]
-
- expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- '.group:focus:hover .group-focus\\:group-hover\\:space-x-4 ~ :not([hidden]) ~ :not([hidden])'
- )
- })
-
- it('should be possible to add multiple unique merge variants to a simple selector', () => {
- let selector = '.text-center'
- let candidate = 'peer-focus:group-hover:text-center'
-
- let formats = [
- { format: ':merge(.group):hover &', isArbitraryVariant: false },
- { format: ':merge(.peer):focus ~ &' },
- ]
-
- expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- '.peer:focus ~ .group:hover .peer-focus\\:group-hover\\:text-center'
- )
- })
-
- it('should be possible to add multiple unique merge variants to a simple selector', () => {
- let selector = '.text-center'
- let candidate = 'group-hover:peer-focus:text-center'
-
- let formats = [
- { format: ':merge(.peer):focus ~ &', isArbitraryVariant: false },
{ format: ':merge(.group):hover &', isArbitraryVariant: false },
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- '.group:hover .peer:focus ~ .group-hover\\:peer-focus\\:text-center'
+ '.group:hover .group-hover\\:hover\\:space-x-4:hover > :not([hidden]) ~ :not([hidden])'
)
})
- it('should be possible to use multiple :merge() calls with different "arguments"', () => {
- let selector = '.foo'
- let candidate = 'peer-focus:group-focus:peer-hover:group-hover:foo'
+ it('should work for group-hover and class dark mode combinations', () => {
+ let selector = '.text-center'
+ let candidate = 'dark:group-hover:text-center'
let formats = [
{ format: ':merge(.group):hover &', isArbitraryVariant: false },
- { format: ':merge(.peer):hover ~ &', isArbitraryVariant: false },
- { format: ':merge(.group):focus &', isArbitraryVariant: false },
- { format: ':merge(.peer):focus ~ &', isArbitraryVariant: false },
+ { format: '.dark &', isArbitraryVariant: false },
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- '.peer:focus:hover ~ .group:focus:hover .peer-focus\\:group-focus\\:peer-hover\\:group-hover\\:foo'
+ '.dark .group:hover .dark\\:group-hover\\:text-center'
)
})
- it('group hover and prose headings combination', () => {
+ it('should work for group-hover and class dark mode combinations (reversed)', () => {
let selector = '.text-center'
- let candidate = 'group-hover:prose-headings:text-center'
+ let candidate = 'group-hover:dark:text-center'
+
let formats = [
- { format: ':where(&) :is(h1, h2, h3, h4)', isArbitraryVariant: false }, // Prose Headings
- { format: ':merge(.group):hover &', isArbitraryVariant: false }, // Group Hover
+ { format: '.dark &' },
+ { format: ':merge(.group):hover &', isArbitraryVariant: false },
]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- '.group:hover :where(.group-hover\\:prose-headings\\:text-center) :is(h1, h2, h3, h4)'
+ '.group:hover .dark .group-hover\\:dark\\:text-center'
)
})
- it('group hover and prose headings combination flipped', () => {
- let selector = '.text-center'
- let candidate = 'prose-headings:group-hover:text-center'
- let formats = [
- { format: ':merge(.group):hover &', isArbitraryVariant: false }, // Group Hover
- { format: ':where(&) :is(h1, h2, h3, h4)', isArbitraryVariant: false }, // Prose Headings
- ]
-
- expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- ':where(.group:hover .prose-headings\\:group-hover\\:text-center) :is(h1, h2, h3, h4)'
- )
- })
-
- it('should be possible to handle a complex utility', () => {
- let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
- let candidate = 'peer-disabled:peer-first-child:group-hover:group-focus:focus:hover:space-x-4'
- let formats = [
- { format: '&:hover', isArbitraryVariant: false }, // Hover
- { format: '&:focus', isArbitraryVariant: false }, // Focus
- { format: ':merge(.group):focus &', isArbitraryVariant: false }, // Group focus
- { format: ':merge(.group):hover &', isArbitraryVariant: false }, // Group hover
- { format: ':merge(.peer):first-child ~ &', isArbitraryVariant: false }, // Peer first-child
- { format: ':merge(.peer):disabled ~ &', isArbitraryVariant: false }, // Peer disabled
- ]
-
- expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- '.peer:disabled:first-child ~ .group:hover:focus .peer-disabled\\:peer-first-child\\:group-hover\\:group-focus\\:focus\\:hover\\:space-x-4:hover:focus > :not([hidden]) ~ :not([hidden])'
- )
- })
-
- it('should match base utilities that are prefixed', () => {
- let context = { tailwindConfig: { prefix: 'tw-' } }
- let selector = '.tw-text-center'
- let candidate = 'tw-text-center'
- let formats = []
-
- expect(finalizeSelector(selector, formats, { candidate, context })).toEqual('.tw-text-center')
- })
-
- it('should prefix classes from variants', () => {
- let context = { tailwindConfig: { prefix: 'tw-' } }
- let selector = '.tw-text-center'
- let candidate = 'foo:tw-text-center'
- let formats = [{ format: '.foo &', isArbitraryVariant: false }]
-
- expect(finalizeSelector(selector, formats, { candidate, context })).toEqual(
- '.tw-foo .foo\\:tw-text-center'
- )
- })
-
- it('should not prefix classes from arbitrary variants', () => {
- let context = { tailwindConfig: { prefix: 'tw-' } }
- let selector = '.tw-text-center'
- let candidate = '[.foo_&]:tw-text-center'
- let formats = [{ format: '.foo &', isArbitraryVariant: true }]
-
- expect(finalizeSelector(selector, formats, { candidate, context })).toEqual(
- '.foo .\\[\\.foo_\\&\\]\\:tw-text-center'
- )
- })
-
- it('Merged selectors with mixed combinators uses the first one', () => {
- // This isn't explicitly specced behavior but it is how it works today
-
- let selector = '.text-center'
- let candidate = 'text-center'
- let formats = [
- { format: ':merge(.group):focus > &', isArbitraryVariant: true },
- { format: ':merge(.group):hover &', isArbitraryVariant: true },
- ]
-
- expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- '.group:hover:focus > .text-center'
- )
- })
-
- describe('real examples', () => {
- it('example a', () => {
- let selector = '.placeholder-red-500::placeholder'
- let candidate = 'hover:placeholder-red-500'
-
- let formats = [{ format: '&:hover', isArbitraryVariant: false }]
-
- expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- '.hover\\:placeholder-red-500:hover::placeholder'
- )
- })
-
- it('example b', () => {
- let selector = '.space-x-4 > :not([hidden]) ~ :not([hidden])'
- let candidate = 'group-hover:hover:space-x-4'
-
- let formats = [
- { format: '&:hover', isArbitraryVariant: false },
- { format: ':merge(.group):hover &', isArbitraryVariant: false },
- ]
-
- expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- '.group:hover .group-hover\\:hover\\:space-x-4:hover > :not([hidden]) ~ :not([hidden])'
- )
- })
-
- it('should work for group-hover and class dark mode combinations', () => {
+ describe('prose-headings', () => {
+ it('should be possible to use hover:prose-headings:text-center', () => {
let selector = '.text-center'
- let candidate = 'dark:group-hover:text-center'
+ let candidate = 'hover:prose-headings:text-center'
- let formats = [
- { format: ':merge(.group):hover &', isArbitraryVariant: false },
- { format: '.dark &', isArbitraryVariant: false },
- ]
+ let formats = [{ format: ':where(&) :is(h1, h2, h3, h4)' }, { format: '&:hover' }]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- '.dark .group:hover .dark\\:group-hover\\:text-center'
+ ':where(.hover\\:prose-headings\\:text-center) :is(h1, h2, h3, h4):hover'
)
})
- it('should work for group-hover and class dark mode combinations (reversed)', () => {
+ it('should be possible to use prose-headings:hover:text-center', () => {
let selector = '.text-center'
- let candidate = 'group-hover:dark:text-center'
+ let candidate = 'prose-headings:hover:text-center'
- let formats = [
- { format: '.dark &' },
- { format: ':merge(.group):hover &', isArbitraryVariant: false },
- ]
+ let formats = [{ format: '&:hover' }, { format: ':where(&) :is(h1, h2, h3, h4)' }]
expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- '.group:hover .dark .group-hover\\:dark\\:text-center'
+ ':where(.prose-headings\\:hover\\:text-center:hover) :is(h1, h2, h3, h4)'
)
})
-
- describe('prose-headings', () => {
- it('should be possible to use hover:prose-headings:text-center', () => {
- let selector = '.text-center'
- let candidate = 'hover:prose-headings:text-center'
-
- let formats = [{ format: ':where(&) :is(h1, h2, h3, h4)' }, { format: '&:hover' }]
-
- expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- ':where(.hover\\:prose-headings\\:text-center) :is(h1, h2, h3, h4):hover'
- )
- })
-
- it('should be possible to use prose-headings:hover:text-center', () => {
- let selector = '.text-center'
- let candidate = 'prose-headings:hover:text-center'
-
- let formats = [{ format: '&:hover' }, { format: ':where(&) :is(h1, h2, h3, h4)' }]
-
- expect(finalizeSelector(selector, formats, { candidate })).toEqual(
- ':where(.prose-headings\\:hover\\:text-center:hover) :is(h1, h2, h3, h4)'
- )
- })
- })
- })
-
- describe('pseudo elements', () => {
- it.each`
- before | after
- ${'&::before'} | ${'&::before'}
- ${'&::before:hover'} | ${'&:hover::before'}
- ${'&:before:hover'} | ${'&:hover:before'}
- ${'&::file-selector-button:hover'} | ${'&::file-selector-button:hover'}
- ${'&:hover::file-selector-button'} | ${'&:hover::file-selector-button'}
- ${'.parent:hover &'} | ${'.parent:hover &'}
- ${'.parent::before &'} | ${'.parent &::before'}
- ${'.parent::before &:hover'} | ${'.parent &:hover::before'}
- ${':where(&::before) :is(h1, h2, h3, h4)'} | ${':where(&) :is(h1, h2, h3, h4)::before'}
- ${':where(&::file-selector-button) :is(h1, h2, h3, h4)'} | ${':where(&::file-selector-button) :is(h1, h2, h3, h4)'}
- ${'#app :is(.dark &::before)'} | ${'#app :is(.dark &)::before'}
- ${'#app :is(:is(.dark &)::before)'} | ${'#app :is(:is(.dark &))::before'}
- ${'#app :is(.foo::file-selector-button)'} | ${'#app :is(.foo)::file-selector-button'}
- ${'#app :is(.foo::-webkit-progress-bar)'} | ${'#app :is(.foo)::-webkit-progress-bar'}
- `('should translate "$before" into "$after"', ({ before, after }) => {
- let result = finalizeSelector('.a', [{ format: before, isArbitraryVariant: false }], {
- candidate: 'a',
- })
-
- expect(result).toEqual(after.replace('&', '.a'))
- })
+ })
+})
+
+describe('pseudo elements', () => {
+ it.each`
+ before | after
+ ${'&::before'} | ${'&::before'}
+ ${'&::before:hover'} | ${'&:hover::before'}
+ ${'&:before:hover'} | ${'&:hover:before'}
+ ${'&::file-selector-button:hover'} | ${'&::file-selector-button:hover'}
+ ${'&:hover::file-selector-button'} | ${'&:hover::file-selector-button'}
+ ${'.parent:hover &'} | ${'.parent:hover &'}
+ ${'.parent::before &'} | ${'.parent &::before'}
+ ${'.parent::before &:hover'} | ${'.parent &:hover::before'}
+ ${':where(&::before) :is(h1, h2, h3, h4)'} | ${':where(&) :is(h1, h2, h3, h4)::before'}
+ ${':where(&::file-selector-button) :is(h1, h2, h3, h4)'} | ${':where(&::file-selector-button) :is(h1, h2, h3, h4)'}
+ ${'#app :is(.dark &::before)'} | ${'#app :is(.dark &)::before'}
+ ${'#app :is(:is(.dark &)::before)'} | ${'#app :is(:is(.dark &))::before'}
+ ${'#app :is(.foo::file-selector-button)'} | ${'#app :is(.foo)::file-selector-button'}
+ ${'#app :is(.foo::-webkit-progress-bar)'} | ${'#app :is(.foo)::-webkit-progress-bar'}
+ ${'.parent::marker li'} | ${'.parent li::marker'}
+ ${'.parent::selection li'} | ${'.parent li::selection'}
+ ${'.parent::placeholder input'} | ${'.parent input::placeholder'}
+ ${'.parent::backdrop dialog'} | ${'.parent dialog::backdrop'}
+ `('should translate "$before" into "$after"', ({ before, after }) => {
+ let result = finalizeSelector('.a', [{ format: before, isArbitraryVariant: false }], {
+ candidate: 'a',
+ })
+
+ expect(result).toEqual(after.replace('&', '.a'))
})
})
diff --git a/tests/generate-rules.test.js b/tests/generate-rules.test.js
index 3aa8592bc..57740c128 100644
--- a/tests/generate-rules.test.js
+++ b/tests/generate-rules.test.js
@@ -1,48 +1,46 @@
import { generateRules } from '../src/lib/generateRules'
import resolveConfig from '../src/public/resolve-config'
import { createContext } from '../src/lib/setupContextUtils'
-import { crosscheck, css } from './util/run'
+import { css } from './util/run'
-crosscheck(() => {
- it('should not generate rules that are incorrect', () => {
- let config = {
- plugins: [
- ({ matchVariant }) => {
- matchVariant('@', (value) => `@container (min-width: ${value})`)
- },
- ],
+it('should not generate rules that are incorrect', () => {
+ let config = {
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('@', (value) => `@container (min-width: ${value})`)
+ },
+ ],
+ }
+ let context = createContext(resolveConfig(config))
+ let rules = generateRules(
+ new Set([
+ // Invalid, missing `-`
+ 'group[:hover]:underline',
+
+ // Invalid, `-` should not be there
+ '@-[200px]:underline',
+
+ // Valid
+ 'group-[:hover]:underline',
+ '@[200px]:underline',
+ ]),
+ context
+ )
+
+ // Ensure we only have 2 valid rules
+ expect(rules).toHaveLength(2)
+
+ // Ensure we have the correct values
+ expect(rules[0][1].toString()).toMatchFormattedCss(css`
+ .group:hover .group-\[\:hover\]\:underline {
+ text-decoration-line: underline;
}
- let context = createContext(resolveConfig(config))
- let rules = generateRules(
- new Set([
- // Invalid, missing `-`
- 'group[:hover]:underline',
-
- // Invalid, `-` should not be there
- '@-[200px]:underline',
-
- // Valid
- 'group-[:hover]:underline',
- '@[200px]:underline',
- ]),
- context
- )
-
- // Ensure we only have 2 valid rules
- expect(rules).toHaveLength(2)
-
- // Ensure we have the correct values
- expect(rules[0][1].toString()).toMatchFormattedCss(css`
- .group:hover .group-\[\:hover\]\:underline {
+ `)
+ expect(rules[1][1].toString()).toMatchFormattedCss(css`
+ @container (min-width: 200px) {
+ .\@\[200px\]\:underline {
text-decoration-line: underline;
}
- `)
- expect(rules[1][1].toString()).toMatchFormattedCss(css`
- @container (min-width: 200px) {
- .\@\[200px\]\:underline {
- text-decoration-line: underline;
- }
- }
- `)
- })
+ }
+ `)
})
diff --git a/tests/getClassList.test.js b/tests/getClassList.test.js
index 671a767ec..63dfea88c 100644
--- a/tests/getClassList.test.js
+++ b/tests/getClassList.test.js
@@ -1,228 +1,225 @@
import resolveConfig from '../src/public/resolve-config'
import plugin from '../src/public/create-plugin'
import { createContext } from '../src/lib/setupContextUtils'
-import { crosscheck } from './util/run'
-crosscheck(() => {
- it('should generate every possible class, without variants', () => {
- let config = {}
+it('should generate every possible class, without variants', () => {
+ let config = {}
- let context = createContext(resolveConfig(config))
- let classes = context.getClassList()
- expect(classes).toBeInstanceOf(Array)
+ let context = createContext(resolveConfig(config))
+ let classes = context.getClassList()
+ expect(classes).toBeInstanceOf(Array)
- // Verify we have a `container` for the 'components' section.
- expect(classes).toContain('container')
+ // Verify we have a `container` for the 'components' section.
+ expect(classes).toContain('container')
- // Verify we handle the DEFAULT case correctly
- expect(classes).toContain('border')
+ // Verify we handle the DEFAULT case correctly
+ expect(classes).toContain('border')
- // Verify we handle negative values correctly
- expect(classes).toContain('-inset-1/4')
- expect(classes).toContain('-m-0')
- expect(classes).not.toContain('-uppercase')
- expect(classes).not.toContain('-opacity-50')
+ // Verify we handle negative values correctly
+ expect(classes).toContain('-inset-1/4')
+ expect(classes).toContain('-m-0')
+ expect(classes).not.toContain('-uppercase')
+ expect(classes).not.toContain('-opacity-50')
- config = { theme: { extend: { margin: { DEFAULT: '5px' } } } }
- context = createContext(resolveConfig(config))
- classes = context.getClassList()
+ config = { theme: { extend: { margin: { DEFAULT: '5px' } } } }
+ context = createContext(resolveConfig(config))
+ classes = context.getClassList()
- expect(classes).not.toContain('-m-DEFAULT')
- })
+ expect(classes).not.toContain('-m-DEFAULT')
+})
- it('should generate every possible class while handling negatives and prefixes', () => {
- let config = { prefix: 'tw-' }
- let context = createContext(resolveConfig(config))
- let classes = context.getClassList()
- expect(classes).toBeInstanceOf(Array)
+it('should generate every possible class while handling negatives and prefixes', () => {
+ let config = { prefix: 'tw-' }
+ let context = createContext(resolveConfig(config))
+ let classes = context.getClassList()
+ expect(classes).toBeInstanceOf(Array)
- // Verify we have a `container` for the 'components' section.
- expect(classes).toContain('tw-container')
+ // Verify we have a `container` for the 'components' section.
+ expect(classes).toContain('tw-container')
- // Verify we handle the DEFAULT case correctly
- expect(classes).toContain('tw-border')
+ // Verify we handle the DEFAULT case correctly
+ expect(classes).toContain('tw-border')
- // Verify we handle negative values correctly
- expect(classes).toContain('-tw-inset-1/4')
- expect(classes).toContain('-tw-m-0')
- expect(classes).not.toContain('-tw-uppercase')
- expect(classes).not.toContain('-tw-opacity-50')
+ // Verify we handle negative values correctly
+ expect(classes).toContain('-tw-inset-1/4')
+ expect(classes).toContain('-tw-m-0')
+ expect(classes).not.toContain('-tw-uppercase')
+ expect(classes).not.toContain('-tw-opacity-50')
- // These utilities do work but there's no reason to generate
- // them alongside the `-{prefix}-{utility}` versions
- expect(classes).not.toContain('tw--inset-1/4')
- expect(classes).not.toContain('tw--m-0')
+ // These utilities do work but there's no reason to generate
+ // them alongside the `-{prefix}-{utility}` versions
+ expect(classes).not.toContain('tw--inset-1/4')
+ expect(classes).not.toContain('tw--m-0')
- config = {
- prefix: 'tw-',
- theme: { extend: { margin: { DEFAULT: '5px' } } },
- }
- context = createContext(resolveConfig(config))
- classes = context.getClassList()
+ config = {
+ prefix: 'tw-',
+ theme: { extend: { margin: { DEFAULT: '5px' } } },
+ }
+ context = createContext(resolveConfig(config))
+ classes = context.getClassList()
- expect(classes).not.toContain('-tw-m-DEFAULT')
- })
+ expect(classes).not.toContain('-tw-m-DEFAULT')
+})
- it('should not generate utilities with opacity by default', () => {
- let config = {}
- let context = createContext(resolveConfig(config))
- let classes = context.getClassList()
+it('should not generate utilities with opacity by default', () => {
+ let config = {}
+ let context = createContext(resolveConfig(config))
+ let classes = context.getClassList()
- expect(classes).not.toContain('bg-red-500/50')
- })
+ expect(classes).not.toContain('bg-red-500/50')
+})
- it('should not include metadata by default', () => {
- let config = {}
- let context = createContext(resolveConfig(config))
- let classes = context.getClassList()
+it('should not include metadata by default', () => {
+ let config = {}
+ let context = createContext(resolveConfig(config))
+ let classes = context.getClassList()
- expect(classes.every((cls) => typeof cls === 'string')).toEqual(true)
+ expect(classes.every((cls) => typeof cls === 'string')).toEqual(true)
- expect(classes).toContain('bg-red-500')
- expect(classes).toContain('text-2xl')
- })
+ expect(classes).toContain('bg-red-500')
+ expect(classes).toContain('text-2xl')
+})
- it('should generate utilities with modifier data when requested', () => {
- let config = {}
- let context = createContext(resolveConfig(config))
- let classes = context.getClassList({ includeMetadata: true })
+it('should generate utilities with modifier data when requested', () => {
+ let config = {}
+ let context = createContext(resolveConfig(config))
+ let classes = context.getClassList({ includeMetadata: true })
- expect(classes).not.toContain('bg-red-500')
- expect(classes).not.toContain('text-2xl')
+ expect(classes).not.toContain('bg-red-500')
+ expect(classes).not.toContain('text-2xl')
- expect(classes).toContainEqual([
- 'bg-red-500',
- {
- modifiers: [
- '0',
- '5',
- '10',
- '20',
- '25',
- '30',
- '40',
- '50',
- '60',
- '70',
- '75',
- '80',
- '90',
- '95',
- '100',
- ],
- },
- ])
- expect(classes).toContainEqual([
- 'text-2xl',
- {
- modifiers: [
- '3',
- '4',
- '5',
- '6',
- '7',
- '8',
- '9',
- '10',
- 'none',
- 'tight',
- 'snug',
- 'normal',
- 'relaxed',
- 'loose',
- ],
- },
- ])
- })
-
- it('should generate plugin-defined utilities with modifier data when requested', () => {
- let config = {
- plugins: [
- plugin(function ({ matchUtilities }) {
- matchUtilities(
- {
- foo: (value) => {
- return { margin: value }
- },
- },
- {
- values: { xl: '32px' },
- modifiers: { bar: 'something' },
- }
- )
- matchUtilities(
- {
- 'foo-negative': (value) => {
- return { margin: value }
- },
- },
- {
- values: { xl: '32px' },
- modifiers: { bar: 'something' },
- supportsNegativeValues: true,
- }
- )
- }),
+ expect(classes).toContainEqual([
+ 'bg-red-500',
+ {
+ modifiers: [
+ '0',
+ '5',
+ '10',
+ '20',
+ '25',
+ '30',
+ '40',
+ '50',
+ '60',
+ '70',
+ '75',
+ '80',
+ '90',
+ '95',
+ '100',
],
- }
- let context = createContext(resolveConfig(config))
- let classes = context.getClassList({ includeMetadata: true })
-
- expect(classes).toContainEqual(['foo-xl', { modifiers: ['bar'] }])
- expect(classes).toContainEqual(['foo-negative-xl', { modifiers: ['bar'] }])
- expect(classes).toContainEqual(['-foo-negative-xl', { modifiers: ['bar'] }])
- expect(classes).not.toContain('foo-xl')
- expect(classes).not.toContain('-foo-xl')
- expect(classes).not.toContainEqual(['-foo-xl', { modifiers: ['bar'] }])
- })
-
- it('should not generate utilities with opacity even if safe-listed', () => {
- let config = {
- safelist: [
- {
- pattern: /^bg-red-(400|500)(\/(40|50))?$/,
- },
+ },
+ ])
+ expect(classes).toContainEqual([
+ 'text-2xl',
+ {
+ modifiers: [
+ '3',
+ '4',
+ '5',
+ '6',
+ '7',
+ '8',
+ '9',
+ '10',
+ 'none',
+ 'tight',
+ 'snug',
+ 'normal',
+ 'relaxed',
+ 'loose',
],
- }
+ },
+ ])
+})
- let context = createContext(resolveConfig(config))
- let classes = context.getClassList()
-
- expect(classes).not.toContain('bg-red-500/50')
- })
-
- it('should not generate utilities that are set to undefined or null to so that they are removed', () => {
- let config = {
- theme: {
- extend: {
- colors: {
- red: null,
- green: undefined,
- blue: {
- 100: null,
- 200: undefined,
+it('should generate plugin-defined utilities with modifier data when requested', () => {
+ let config = {
+ plugins: [
+ plugin(function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ foo: (value) => {
+ return { margin: value }
},
},
+ {
+ values: { xl: '32px' },
+ modifiers: { bar: 'something' },
+ }
+ )
+ matchUtilities(
+ {
+ 'foo-negative': (value) => {
+ return { margin: value }
+ },
+ },
+ {
+ values: { xl: '32px' },
+ modifiers: { bar: 'something' },
+ supportsNegativeValues: true,
+ }
+ )
+ }),
+ ],
+ }
+ let context = createContext(resolveConfig(config))
+ let classes = context.getClassList({ includeMetadata: true })
+
+ expect(classes).toContainEqual(['foo-xl', { modifiers: ['bar'] }])
+ expect(classes).toContainEqual(['foo-negative-xl', { modifiers: ['bar'] }])
+ expect(classes).toContainEqual(['-foo-negative-xl', { modifiers: ['bar'] }])
+ expect(classes).not.toContain('foo-xl')
+ expect(classes).not.toContain('-foo-xl')
+ expect(classes).not.toContainEqual(['-foo-xl', { modifiers: ['bar'] }])
+})
+
+it('should not generate utilities with opacity even if safe-listed', () => {
+ let config = {
+ safelist: [
+ {
+ pattern: /^bg-red-(400|500)(\/(40|50))?$/,
+ },
+ ],
+ }
+
+ let context = createContext(resolveConfig(config))
+ let classes = context.getClassList()
+
+ expect(classes).not.toContain('bg-red-500/50')
+})
+
+it('should not generate utilities that are set to undefined or null to so that they are removed', () => {
+ let config = {
+ theme: {
+ extend: {
+ colors: {
+ red: null,
+ green: undefined,
+ blue: {
+ 100: null,
+ 200: undefined,
+ },
},
},
- safelist: [
- {
- pattern: /^bg-(red|green|blue)-.*$/,
- },
- ],
- }
+ },
+ safelist: [
+ {
+ pattern: /^bg-(red|green|blue)-.*$/,
+ },
+ ],
+ }
- let context = createContext(resolveConfig(config))
- let classes = context.getClassList()
+ let context = createContext(resolveConfig(config))
+ let classes = context.getClassList()
- expect(classes).not.toContain('bg-red-100') // Red is `null`
+ expect(classes).not.toContain('bg-red-100') // Red is `null`
- expect(classes).not.toContain('bg-green-100') // Green is `undefined`
+ expect(classes).not.toContain('bg-green-100') // Green is `undefined`
- expect(classes).not.toContain('bg-blue-100') // Blue.100 is `null`
- expect(classes).not.toContain('bg-blue-200') // Blue.200 is `undefined`
+ expect(classes).not.toContain('bg-blue-100') // Blue.100 is `null`
+ expect(classes).not.toContain('bg-blue-200') // Blue.200 is `undefined`
- expect(classes).toContain('bg-blue-50')
- expect(classes).toContain('bg-blue-300')
- })
+ expect(classes).toContain('bg-blue-50')
+ expect(classes).toContain('bg-blue-300')
})
diff --git a/tests/getSortOrder.test.js b/tests/getSortOrder.test.js
index 4526c4874..0863d268d 100644
--- a/tests/getSortOrder.test.js
+++ b/tests/getSortOrder.test.js
@@ -1,7 +1,6 @@
import resolveConfig from '../src/public/resolve-config'
import { createContext } from '../src/lib/setupContextUtils'
import bigSign from '../src/util/bigSign'
-import { crosscheck } from './util/run'
/**
* This is a function that the prettier-plugin-tailwindcss would use. It would
@@ -25,121 +24,119 @@ function defaultSort(arrayOfTuples) {
.join(' ')
}
-crosscheck(() => {
- it('should return a list of tuples with the sort order', () => {
- let input = 'font-bold underline hover:font-medium unknown'
- let config = {}
- let context = createContext(resolveConfig(config))
- expect(context.getClassOrder(input.split(' '))).toEqual([
- ['font-bold', expect.any(BigInt)],
- ['underline', expect.any(BigInt)],
- ['hover:font-medium', expect.any(BigInt)],
+it('should return a list of tuples with the sort order', () => {
+ let input = 'font-bold underline hover:font-medium unknown'
+ let config = {}
+ let context = createContext(resolveConfig(config))
+ expect(context.getClassOrder(input.split(' '))).toEqual([
+ ['font-bold', expect.any(BigInt)],
+ ['underline', expect.any(BigInt)],
+ ['hover:font-medium', expect.any(BigInt)],
- // Unknown values receive `null`
- ['unknown', null],
- ])
- })
+ // Unknown values receive `null`
+ ['unknown', null],
+ ])
+})
- it.each([
- // Utitlies
- ['px-3 p-1 py-3', 'p-1 px-3 py-3'],
+it.each([
+ // Utitlies
+ ['px-3 p-1 py-3', 'p-1 px-3 py-3'],
- // Utitlies and components
- ['px-4 container', 'container px-4'],
+ // Utitlies and components
+ ['px-4 container', 'container px-4'],
- // Utilities with variants
- ['px-3 focus:hover:p-3 hover:p-1 py-3', 'px-3 py-3 hover:p-1 focus:hover:p-3'],
+ // Utilities with variants
+ ['px-3 focus:hover:p-3 hover:p-1 py-3', 'px-3 py-3 hover:p-1 focus:hover:p-3'],
- // Utitlies with important
- ['px-3 !py-4', '!py-4 px-3'],
- ['!py-4 px-3', '!py-4 px-3'],
+ // Utitlies with important
+ ['px-3 !py-4', '!py-4 px-3'],
+ ['!py-4 px-3', '!py-4 px-3'],
- // Components with variants
- ['hover:container container', 'container hover:container'],
+ // Components with variants
+ ['hover:container container', 'container hover:container'],
- // Components and utilities with variants
- [
- 'focus:hover:container hover:underline hover:container p-1',
- 'p-1 hover:container focus:hover:container hover:underline',
- ],
+ // Components and utilities with variants
+ [
+ 'focus:hover:container hover:underline hover:container p-1',
+ 'p-1 hover:container focus:hover:container hover:underline',
+ ],
- // Leave user css order alone, and move to the front
- ['b p-1 a', 'b a p-1'],
- ['hover:b focus:p-1 a', 'hover:b a focus:p-1'],
+ // Leave user css order alone, and move to the front
+ ['b p-1 a', 'b a p-1'],
+ ['hover:b focus:p-1 a', 'hover:b a focus:p-1'],
- // Add special treatment for `group` and `peer`
- ['a peer container underline', 'a peer container underline'],
- ])('should sort "%s" based on the order we generate them in to "%s"', (input, output) => {
- let config = {}
+ // Add special treatment for `group` and `peer`
+ ['a peer container underline', 'a peer container underline'],
+])('should sort "%s" based on the order we generate them in to "%s"', (input, output) => {
+ let config = {}
+ let context = createContext(resolveConfig(config))
+ expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
+})
+
+it.each([
+ // Utitlies
+ ['tw-px-3 tw-p-1 tw-py-3', 'tw-p-1 tw-px-3 tw-py-3'],
+
+ // Utitlies and components
+ ['tw-px-4 tw-container', 'tw-container tw-px-4'],
+
+ // Utilities with variants
+ [
+ 'tw-px-3 focus:hover:tw-p-3 hover:tw-p-1 tw-py-3',
+ 'tw-px-3 tw-py-3 hover:tw-p-1 focus:hover:tw-p-3',
+ ],
+
+ // Utitlies with important
+ ['tw-px-3 !tw-py-4', '!tw-py-4 tw-px-3'],
+ ['!tw-py-4 tw-px-3', '!tw-py-4 tw-px-3'],
+
+ // Components with variants
+ ['hover:tw-container tw-container', 'tw-container hover:tw-container'],
+
+ // Components and utilities with variants
+ [
+ 'focus:hover:tw-container hover:tw-underline hover:tw-container tw-p-1',
+ 'tw-p-1 hover:tw-container focus:hover:tw-container hover:tw-underline',
+ ],
+
+ // Leave user css order alone, and move to the front
+ ['b tw-p-1 a', 'b a tw-p-1'],
+ ['hover:b focus:tw-p-1 a', 'hover:b a focus:tw-p-1'],
+
+ // Add special treatment for `group` and `peer`
+ ['a tw-peer tw-container tw-underline', 'a tw-peer tw-container tw-underline'],
+])(
+ 'should sort "%s" with prefixex based on the order we generate them in to "%s"',
+ (input, output) => {
+ let config = { prefix: 'tw-' }
let context = createContext(resolveConfig(config))
expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
- })
+ }
+)
- it.each([
- // Utitlies
- ['tw-px-3 tw-p-1 tw-py-3', 'tw-p-1 tw-px-3 tw-py-3'],
-
- // Utitlies and components
- ['tw-px-4 tw-container', 'tw-container tw-px-4'],
-
- // Utilities with variants
+it('sorts classes deterministically across multiple class lists', () => {
+ let classes = [
[
- 'tw-px-3 focus:hover:tw-p-3 hover:tw-p-1 tw-py-3',
- 'tw-px-3 tw-py-3 hover:tw-p-1 focus:hover:tw-p-3',
+ 'a-class px-3 p-1 b-class py-3 bg-red-500 bg-blue-500',
+ 'a-class b-class bg-blue-500 bg-red-500 p-1 px-3 py-3',
],
-
- // Utitlies with important
- ['tw-px-3 !tw-py-4', '!tw-py-4 tw-px-3'],
- ['!tw-py-4 tw-px-3', '!tw-py-4 tw-px-3'],
-
- // Components with variants
- ['hover:tw-container tw-container', 'tw-container hover:tw-container'],
-
- // Components and utilities with variants
[
- 'focus:hover:tw-container hover:tw-underline hover:tw-container tw-p-1',
- 'tw-p-1 hover:tw-container focus:hover:tw-container hover:tw-underline',
+ 'px-3 b-class p-1 py-3 bg-blue-500 a-class bg-red-500',
+ 'b-class a-class bg-blue-500 bg-red-500 p-1 px-3 py-3',
],
+ ]
- // Leave user css order alone, and move to the front
- ['b tw-p-1 a', 'b a tw-p-1'],
- ['hover:b focus:tw-p-1 a', 'hover:b a focus:tw-p-1'],
+ let config = {}
- // Add special treatment for `group` and `peer`
- ['a tw-peer tw-container tw-underline', 'a tw-peer tw-container tw-underline'],
- ])(
- 'should sort "%s" with prefixex based on the order we generate them in to "%s"',
- (input, output) => {
- let config = { prefix: 'tw-' }
- let context = createContext(resolveConfig(config))
- expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
- }
- )
+ // Same context, different class lists
+ let context = createContext(resolveConfig(config))
+ for (const [input, output] of classes) {
+ expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
+ }
- it('sorts classes deterministically across multiple class lists', () => {
- let classes = [
- [
- 'a-class px-3 p-1 b-class py-3 bg-red-500 bg-blue-500',
- 'a-class b-class bg-blue-500 bg-red-500 p-1 px-3 py-3',
- ],
- [
- 'px-3 b-class p-1 py-3 bg-blue-500 a-class bg-red-500',
- 'b-class a-class bg-blue-500 bg-red-500 p-1 px-3 py-3',
- ],
- ]
-
- let config = {}
-
- // Same context, different class lists
- let context = createContext(resolveConfig(config))
- for (const [input, output] of classes) {
- expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
- }
-
- // Different context, different class lists
- for (const [input, output] of classes) {
- context = createContext(resolveConfig(config))
- expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
- }
- })
+ // Different context, different class lists
+ for (const [input, output] of classes) {
+ context = createContext(resolveConfig(config))
+ expect(defaultSort(context.getClassOrder(input.split(' ')))).toEqual(output)
+ }
})
diff --git a/tests/getVariants.test.js b/tests/getVariants.test.js
index 8d79f8243..114d80824 100644
--- a/tests/getVariants.test.js
+++ b/tests/getVariants.test.js
@@ -2,188 +2,185 @@ import postcss from 'postcss'
import selectorParser from 'postcss-selector-parser'
import resolveConfig from '../src/public/resolve-config'
import { createContext } from '../src/lib/setupContextUtils'
-import { crosscheck } from './util/run'
-crosscheck(() => {
- it('should return a list of variants with meta information about the variant', () => {
- let config = {}
- let context = createContext(resolveConfig(config))
+it('should return a list of variants with meta information about the variant', () => {
+ let config = {}
+ let context = createContext(resolveConfig(config))
- let variants = context.getVariants()
+ let variants = context.getVariants()
- expect(variants).toContainEqual({
- name: 'hover',
- isArbitrary: false,
- hasDash: true,
- values: [],
- selectors: expect.any(Function),
- })
-
- expect(variants).toContainEqual({
- name: 'group',
- isArbitrary: true,
- hasDash: true,
- values: expect.any(Array),
- selectors: expect.any(Function),
- })
-
- // `group-hover` now belongs to the `group` variant. The information exposed for the `group`
- // variant is all you need.
- expect(variants.find((v) => v.name === 'group-hover')).toBeUndefined()
+ expect(variants).toContainEqual({
+ name: 'hover',
+ isArbitrary: false,
+ hasDash: true,
+ values: [],
+ selectors: expect.any(Function),
})
- it('should provide selectors for simple variants', () => {
- let config = {}
- let context = createContext(resolveConfig(config))
-
- let variants = context.getVariants()
-
- let variant = variants.find((v) => v.name === 'hover')
- expect(variant.selectors()).toEqual(['&:hover'])
+ expect(variants).toContainEqual({
+ name: 'group',
+ isArbitrary: true,
+ hasDash: true,
+ values: expect.any(Array),
+ selectors: expect.any(Function),
})
- it('should provide selectors for parallel variants', () => {
- let config = {}
- let context = createContext(resolveConfig(config))
+ // `group-hover` now belongs to the `group` variant. The information exposed for the `group`
+ // variant is all you need.
+ expect(variants.find((v) => v.name === 'group-hover')).toBeUndefined()
+})
- let variants = context.getVariants()
+it('should provide selectors for simple variants', () => {
+ let config = {}
+ let context = createContext(resolveConfig(config))
- let variant = variants.find((v) => v.name === 'marker')
- expect(variant.selectors()).toEqual(['& *::marker', '&::marker'])
- })
+ let variants = context.getVariants()
- it('should provide selectors for complex matchVariant variants like `group`', () => {
- let config = {}
- let context = createContext(resolveConfig(config))
+ let variant = variants.find((v) => v.name === 'hover')
+ expect(variant.selectors()).toEqual(['&:hover'])
+})
- let variants = context.getVariants()
+it('should provide selectors for parallel variants', () => {
+ let config = {}
+ let context = createContext(resolveConfig(config))
- let variant = variants.find((v) => v.name === 'group')
- expect(variant.selectors()).toEqual(['.group &'])
- expect(variant.selectors({})).toEqual(['.group &'])
- expect(variant.selectors({ value: 'hover' })).toEqual(['.group:hover &'])
- expect(variant.selectors({ value: '.foo_&' })).toEqual(['.foo .group &'])
- expect(variant.selectors({ modifier: 'foo', value: 'hover' })).toEqual(['.group\\/foo:hover &'])
- expect(variant.selectors({ modifier: 'foo', value: '.foo_&' })).toEqual(['.foo .group\\/foo &'])
- })
+ let variants = context.getVariants()
- it('should provide selectors for variants with atrules', () => {
- let config = {}
- let context = createContext(resolveConfig(config))
+ let variant = variants.find((v) => v.name === 'marker')
+ expect(variant.selectors()).toEqual(['& *::marker', '&::marker'])
+})
- let variants = context.getVariants()
+it('should provide selectors for complex matchVariant variants like `group`', () => {
+ let config = {}
+ let context = createContext(resolveConfig(config))
- let variant = variants.find((v) => v.name === 'supports')
- expect(variant.selectors({ value: 'display:grid' })).toEqual(['@supports (display:grid)'])
- expect(variant.selectors({ value: 'aspect-ratio' })).toEqual([
- '@supports (aspect-ratio: var(--tw))',
- ])
- })
+ let variants = context.getVariants()
- it('should provide selectors for custom plugins that do a combination of parallel variants with modifiers with arbitrary values and with atrules', () => {
- let config = {
- plugins: [
- function ({ matchVariant }) {
- matchVariant('foo', (value, { modifier }) => {
- return [
- `
+ let variant = variants.find((v) => v.name === 'group')
+ expect(variant.selectors()).toEqual(['.group &'])
+ expect(variant.selectors({})).toEqual(['.group &'])
+ expect(variant.selectors({ value: 'hover' })).toEqual(['.group:hover &'])
+ expect(variant.selectors({ value: '.foo_&' })).toEqual(['.foo .group &'])
+ expect(variant.selectors({ modifier: 'foo', value: 'hover' })).toEqual(['.group\\/foo:hover &'])
+ expect(variant.selectors({ modifier: 'foo', value: '.foo_&' })).toEqual(['.foo .group\\/foo &'])
+})
+
+it('should provide selectors for variants with atrules', () => {
+ let config = {}
+ let context = createContext(resolveConfig(config))
+
+ let variants = context.getVariants()
+
+ let variant = variants.find((v) => v.name === 'supports')
+ expect(variant.selectors({ value: 'display:grid' })).toEqual(['@supports (display:grid)'])
+ expect(variant.selectors({ value: 'aspect-ratio' })).toEqual([
+ '@supports (aspect-ratio: var(--tw))',
+ ])
+})
+
+it('should provide selectors for custom plugins that do a combination of parallel variants with modifiers with arbitrary values and with atrules', () => {
+ let config = {
+ plugins: [
+ function ({ matchVariant }) {
+ matchVariant('foo', (value, { modifier }) => {
+ return [
+ `
@supports (foo: ${modifier}) {
- @media (width <= 400px) {
+ @media (max-width: 400px) {
&:hover
}
}
`,
- `.${modifier}\\/${value} &:focus`,
- ]
- })
- },
- ],
- }
- let context = createContext(resolveConfig(config))
+ `.${modifier}\\/${value} &:focus`,
+ ]
+ })
+ },
+ ],
+ }
+ let context = createContext(resolveConfig(config))
- let variants = context.getVariants()
+ let variants = context.getVariants()
- let variant = variants.find((v) => v.name === 'foo')
- expect(variant.selectors({ modifier: 'bar', value: 'baz' })).toEqual([
- '@supports (foo: bar) { @media (width <= 400px) { &:hover } }',
- '.bar\\/baz &:focus',
- ])
- })
-
- it('should work for plugins that still use the modifySelectors API', () => {
- let config = {
- plugins: [
- function ({ addVariant }) {
- addVariant('foo', ({ modifySelectors, container }) => {
- // Manually mutating the selector
- modifySelectors(({ selector }) => {
- return selectorParser((selectors) => {
- selectors.walkClasses((classNode) => {
- classNode.value = `foo:${classNode.value}`
- classNode.parent.insertBefore(classNode, selectorParser().astSync(`.foo `))
- })
- }).processSync(selector)
- })
-
- // Manually wrap in supports query
- let wrapper = postcss.atRule({ name: 'supports', params: 'display: grid' })
- let nodes = container.nodes
- container.removeAll()
- wrapper.append(nodes)
- container.append(wrapper)
- })
- },
- ],
- }
- let context = createContext(resolveConfig(config))
-
- let variants = context.getVariants()
-
- let variant = variants.find((v) => v.name === 'foo')
- expect(variant.selectors({})).toEqual(['@supports (display: grid) { .foo .foo\\:& }'])
- })
-
- it('should special case the `@`', () => {
- let config = {
- plugins: [
- ({ matchVariant }) => {
- matchVariant(
- '@',
- (value, { modifier }) => `@container ${modifier ?? ''} (min-width: ${value})`,
- {
- modifiers: 'any',
- values: {
- xs: '20rem',
- sm: '24rem',
- md: '28rem',
- lg: '32rem',
- xl: '36rem',
- '2xl': '42rem',
- '3xl': '48rem',
- '4xl': '56rem',
- '5xl': '64rem',
- '6xl': '72rem',
- '7xl': '80rem',
- },
- }
- )
- },
- ],
- }
- let context = createContext(resolveConfig(config))
-
- let variants = context.getVariants()
-
- let variant = variants.find((v) => v.name === '@')
- expect(variant).toEqual({
- name: '@',
- isArbitrary: true,
- hasDash: false,
- values: expect.any(Array),
- selectors: expect.any(Function),
- })
- expect(variant.selectors({ value: 'xs', modifier: 'foo' })).toEqual([
- '@container foo (min-width: 20rem)',
- ])
- })
+ let variant = variants.find((v) => v.name === 'foo')
+ expect(variant.selectors({ modifier: 'bar', value: 'baz' })).toEqual([
+ '@supports (foo: bar) { @media (max-width: 400px) { &:hover } }',
+ '.bar\\/baz &:focus',
+ ])
+})
+
+it('should work for plugins that still use the modifySelectors API', () => {
+ let config = {
+ plugins: [
+ function ({ addVariant }) {
+ addVariant('foo', ({ modifySelectors, container }) => {
+ // Manually mutating the selector
+ modifySelectors(({ selector }) => {
+ return selectorParser((selectors) => {
+ selectors.walkClasses((classNode) => {
+ classNode.value = `foo:${classNode.value}`
+ classNode.parent.insertBefore(classNode, selectorParser().astSync(`.foo `))
+ })
+ }).processSync(selector)
+ })
+
+ // Manually wrap in supports query
+ let wrapper = postcss.atRule({ name: 'supports', params: 'display: grid' })
+ let nodes = container.nodes
+ container.removeAll()
+ wrapper.append(nodes)
+ container.append(wrapper)
+ })
+ },
+ ],
+ }
+ let context = createContext(resolveConfig(config))
+
+ let variants = context.getVariants()
+
+ let variant = variants.find((v) => v.name === 'foo')
+ expect(variant.selectors({})).toEqual(['@supports (display: grid) { .foo .foo\\:& }'])
+})
+
+it('should special case the `@`', () => {
+ let config = {
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant(
+ '@',
+ (value, { modifier }) => `@container ${modifier ?? ''} (min-width: ${value})`,
+ {
+ modifiers: 'any',
+ values: {
+ xs: '20rem',
+ sm: '24rem',
+ md: '28rem',
+ lg: '32rem',
+ xl: '36rem',
+ '2xl': '42rem',
+ '3xl': '48rem',
+ '4xl': '56rem',
+ '5xl': '64rem',
+ '6xl': '72rem',
+ '7xl': '80rem',
+ },
+ }
+ )
+ },
+ ],
+ }
+ let context = createContext(resolveConfig(config))
+
+ let variants = context.getVariants()
+
+ let variant = variants.find((v) => v.name === '@')
+ expect(variant).toEqual({
+ name: '@',
+ isArbitrary: true,
+ hasDash: false,
+ values: expect.any(Array),
+ selectors: expect.any(Function),
+ })
+ expect(variant.selectors({ value: 'xs', modifier: 'foo' })).toEqual([
+ '@container foo (min-width: 20rem)',
+ ])
})
diff --git a/tests/import-processing-a.css b/tests/import-processing-a.css
new file mode 100644
index 000000000..65dd5f63a
--- /dev/null
+++ b/tests/import-processing-a.css
@@ -0,0 +1 @@
+@tailwind utilities;
diff --git a/tests/import-processing-b.css b/tests/import-processing-b.css
new file mode 100644
index 000000000..69d5390f1
--- /dev/null
+++ b/tests/import-processing-b.css
@@ -0,0 +1,5 @@
+@layer utilities {
+ .foo {
+ color: red;
+ }
+}
diff --git a/tests/import-processing-c.css b/tests/import-processing-c.css
new file mode 100644
index 000000000..508c10fe2
--- /dev/null
+++ b/tests/import-processing-c.css
@@ -0,0 +1,5 @@
+@tailwind utilities;
+
+.foo {
+ color: red;
+}
diff --git a/tests/import-processing-c.js b/tests/import-processing-c.js
new file mode 100644
index 000000000..164503d5e
--- /dev/null
+++ b/tests/import-processing-c.js
@@ -0,0 +1,9 @@
+module.exports = {
+ darkMode: 'class',
+ content: [
+ {
+ raw: `
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+}
diff --git a/tests/import-processing.test.js b/tests/import-processing.test.js
new file mode 100644
index 000000000..ef59be348
--- /dev/null
+++ b/tests/import-processing.test.js
@@ -0,0 +1,76 @@
+import { html, css, run } from './util/run'
+
+describe('import processing', () => {
+ it('should be possible to import another css file', async () => {
+ let config = {
+ darkMode: 'class',
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @import './import-processing-a.css';
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .underline {
+ text-decoration-line: underline;
+ }
+ `)
+ })
+
+ it('should be possible to import another css file after @tailwind directive', async () => {
+ let config = {
+ darkMode: 'class',
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+
+ @import './import-processing-b.css';
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .underline {
+ text-decoration-line: underline;
+ }
+
+ .foo {
+ color: red;
+ }
+ `)
+ })
+
+ it('should be possible to add @config before @import statements', async () => {
+ let input = css`
+ @config "./import-processing-c.js";
+ @import './import-processing-c.css';
+ `
+
+ let result = await run(input)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .underline {
+ text-decoration-line: underline;
+ }
+
+ .foo {
+ color: red;
+ }
+ `)
+ })
+})
diff --git a/tests/import-syntax.test.js b/tests/import-syntax.test.js
index 992ef81d5..99f5163f7 100644
--- a/tests/import-syntax.test.js
+++ b/tests/import-syntax.test.js
@@ -1,129 +1,82 @@
-import { crosscheck, run, html, css, defaults } from './util/run'
+import { run, html, css, defaults } from './util/run'
-crosscheck(({ stable, oxide }) => {
- test('using @import instead of @tailwind', () => {
- let config = {
- content: [
- {
- raw: html`
- Hello world!
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- function ({ addBase }) {
- addBase({
- h1: {
- fontSize: '32px',
- },
- })
- },
- ],
- }
+test('using @import instead of @tailwind', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+ Hello world!
+
+
+
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ function ({ addBase }) {
+ addBase({
+ h1: {
+ fontSize: '32px',
+ },
+ })
+ },
+ ],
+ }
- let input = css`
- @import 'tailwindcss/base';
- @import 'tailwindcss/components';
- @import 'tailwindcss/utilities';
- `
+ let input = css`
+ @import 'tailwindcss/base';
+ @import 'tailwindcss/components';
+ @import 'tailwindcss/utilities';
+ `
- return run(input, config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- h1 {
- font-size: 32px;
- }
- ${defaults}
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ h1 {
+ font-size: 32px;
+ }
+ ${defaults}
+ .container {
+ width: 100%;
+ }
+ @media (min-width: 640px) {
.container {
- width: 100%;
+ max-width: 640px;
}
- @media (min-width: 640px) {
- .container {
- max-width: 640px;
- }
- }
- @media (min-width: 768px) {
- .container {
- max-width: 768px;
- }
- }
- @media (min-width: 1024px) {
- .container {
- max-width: 1024px;
- }
- }
- @media (min-width: 1280px) {
- .container {
- max-width: 1280px;
- }
- }
- @media (min-width: 1536px) {
- .container {
- max-width: 1536px;
- }
- }
- .mt-6 {
- margin-top: 1.5rem;
- }
- .bg-black {
- --tw-bg-opacity: 1;
- background-color: rgb(0 0 0 / var(--tw-bg-opacity));
- }
- @media (min-width: 768px) {
- .md\:hover\:text-center:hover {
- text-align: center;
- }
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- h1 {
- font-size: 32px;
- }
- ${defaults}
+ }
+ @media (min-width: 768px) {
.container {
- width: 100%;
+ max-width: 768px;
}
- @media (min-width: 640px) {
- .container {
- max-width: 640px;
- }
+ }
+ @media (min-width: 1024px) {
+ .container {
+ max-width: 1024px;
}
- @media (min-width: 768px) {
- .container {
- max-width: 768px;
- }
+ }
+ @media (min-width: 1280px) {
+ .container {
+ max-width: 1280px;
}
- @media (min-width: 1024px) {
- .container {
- max-width: 1024px;
- }
+ }
+ @media (min-width: 1536px) {
+ .container {
+ max-width: 1536px;
}
- @media (min-width: 1280px) {
- .container {
- max-width: 1280px;
- }
+ }
+ .mt-6 {
+ margin-top: 1.5rem;
+ }
+ .bg-black {
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 0 0 / var(--tw-bg-opacity));
+ }
+ @media (min-width: 768px) {
+ .md\:hover\:text-center:hover {
+ text-align: center;
}
- @media (min-width: 1536px) {
- .container {
- max-width: 1536px;
- }
- }
- .mt-6 {
- margin-top: 1.5rem;
- }
- .bg-black {
- background-color: #000;
- }
- @media (min-width: 768px) {
- .md\:hover\:text-center:hover {
- text-align: center;
- }
- }
- `)
- })
+ }
+ `)
})
})
diff --git a/tests/important-boolean.test.js b/tests/important-boolean.test.js
index 895ca053c..d66f21edc 100644
--- a/tests/important-boolean.test.js
+++ b/tests/important-boolean.test.js
@@ -2,230 +2,228 @@ import fs from 'fs'
import path from 'path'
import * as sharedState from '../src/lib/sharedState'
-import { crosscheck, run, html, css, defaults } from './util/run'
+import { run, html, css, defaults } from './util/run'
-crosscheck(() => {
- test('important boolean', () => {
- let config = {
- important: true,
- darkMode: 'class',
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- function ({ addComponents, addUtilities }) {
- addComponents(
- {
- '.btn': {
- button: 'yes',
- },
+test('important boolean', () => {
+ let config = {
+ important: true,
+ darkMode: 'class',
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ function ({ addComponents, addUtilities }) {
+ addComponents(
+ {
+ '.btn': {
+ button: 'yes',
},
- { respectImportant: true }
- )
- addComponents(
- {
- '@font-face': {
- 'font-family': 'Inter',
- },
- '@page': {
- margin: '1cm',
- },
+ },
+ { respectImportant: true }
+ )
+ addComponents(
+ {
+ '@font-face': {
+ 'font-family': 'Inter',
},
- { respectImportant: true }
- )
- addUtilities(
- {
- '.custom-util': {
- button: 'no',
- },
+ '@page': {
+ margin: '1cm',
},
- { respectImportant: false }
- )
- },
- ],
+ },
+ { respectImportant: true }
+ )
+ addUtilities(
+ {
+ '.custom-util': {
+ button: 'no',
+ },
+ },
+ { respectImportant: false }
+ )
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind base;
+ @tailwind components;
+ @layer components {
+ .custom-component {
+ @apply font-bold;
+ }
+ .custom-important-component {
+ @apply text-center !important;
+ }
}
+ @tailwind utilities;
+ `
- let input = css`
- @tailwind base;
- @tailwind components;
- @layer components {
- .custom-component {
- @apply font-bold;
- }
- .custom-important-component {
- @apply text-center !important;
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults}
+ .container {
+ width: 100%;
+ }
+ @media (min-width: 640px) {
+ .container {
+ max-width: 640px;
}
}
+ @media (min-width: 768px) {
+ .container {
+ max-width: 768px;
+ }
+ }
+ @media (min-width: 1024px) {
+ .container {
+ max-width: 1024px;
+ }
+ }
+ @media (min-width: 1280px) {
+ .container {
+ max-width: 1280px;
+ }
+ }
+ @media (min-width: 1536px) {
+ .container {
+ max-width: 1536px;
+ }
+ }
+ .btn {
+ button: yes !important;
+ }
+ @font-face {
+ font-family: Inter;
+ }
+ @page {
+ margin: 1cm;
+ }
+ .custom-component {
+ font-weight: 700;
+ }
+ .custom-important-component {
+ text-align: center !important;
+ }
+ @keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+ }
+ .animate-spin {
+ animation: 1s linear infinite spin !important;
+ }
+ .font-bold {
+ font-weight: 700 !important;
+ }
+ .custom-util {
+ button: no;
+ }
+ .group:hover .group-hover\:focus-within\:text-left:focus-within {
+ text-align: left !important;
+ }
+ :is([dir='rtl'] .rtl\:active\:text-center:active) {
+ text-align: center !important;
+ }
+ @media (prefers-reduced-motion: no-preference) {
+ .motion-safe\:hover\:text-center:hover {
+ text-align: center !important;
+ }
+ }
+ :is(.dark .dark\:focus\:text-left:focus) {
+ text-align: left !important;
+ }
+ @media (min-width: 768px) {
+ .md\:hover\:text-right:hover {
+ text-align: right !important;
+ }
+ }
+ `)
+ })
+})
+
+// This is in a describe block so we can use `afterEach` :)
+describe('duplicate elision', () => {
+ let filePath = path.resolve(__dirname, './important-boolean-duplicates.test.html')
+
+ afterEach(async () => await fs.promises.unlink(filePath))
+
+ test('important rules are not duplicated when rebuilding', async () => {
+ let config = {
+ important: true,
+ content: [filePath],
+ }
+
+ await fs.promises.writeFile(
+ config.content[0],
+ html`
+
+
+ `
+ )
+
+ let input = css`
@tailwind utilities;
`
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- .container {
- width: 100%;
- }
- @media (min-width: 640px) {
- .container {
- max-width: 640px;
- }
- }
- @media (min-width: 768px) {
- .container {
- max-width: 768px;
- }
- }
- @media (min-width: 1024px) {
- .container {
- max-width: 1024px;
- }
- }
- @media (min-width: 1280px) {
- .container {
- max-width: 1280px;
- }
- }
- @media (min-width: 1536px) {
- .container {
- max-width: 1536px;
- }
- }
- .btn {
- button: yes !important;
- }
- @font-face {
- font-family: Inter;
- }
- @page {
- margin: 1cm;
- }
- .custom-component {
- font-weight: 700;
- }
- .custom-important-component {
- text-align: center !important;
- }
- @keyframes spin {
- to {
- transform: rotate(360deg);
- }
- }
- .animate-spin {
- animation: 1s linear infinite spin !important;
- }
- .font-bold {
- font-weight: 700 !important;
- }
- .custom-util {
- button: no;
- }
- .group:hover .group-hover\:focus-within\:text-left:focus-within {
- text-align: left !important;
- }
- :is([dir='rtl'] .rtl\:active\:text-center:active) {
- text-align: center !important;
- }
- @media (prefers-reduced-motion: no-preference) {
- .motion-safe\:hover\:text-center:hover {
- text-align: center !important;
- }
- }
- :is(.dark .dark\:focus\:text-left:focus) {
- text-align: left !important;
- }
- @media (min-width: 768px) {
- .md\:hover\:text-right:hover {
- text-align: right !important;
- }
- }
- `)
- })
- })
+ let result = await run(input, config)
+ let allContexts = Array.from(sharedState.contextMap.values())
- // This is in a describe block so we can use `afterEach` :)
- describe('duplicate elision', () => {
- let filePath = path.resolve(__dirname, './important-boolean-duplicates.test.html')
+ let context = allContexts[allContexts.length - 1]
- afterEach(async () => await fs.promises.unlink(filePath))
+ let ruleCacheSize1 = context.ruleCache.size
- test('important rules are not duplicated when rebuilding', async () => {
- let config = {
- important: true,
- content: [filePath],
+ expect(result.css).toMatchFormattedCss(css`
+ .ml-2 {
+ margin-left: 0.5rem !important;
}
+ .ml-4 {
+ margin-left: 1rem !important;
+ }
+ `)
- await fs.promises.writeFile(
- config.content[0],
- html`
-
-
- `
- )
-
- let input = css`
- @tailwind utilities;
+ await fs.promises.writeFile(
+ config.content[0],
+ html`
+
+
`
+ )
- let result = await run(input, config)
- let allContexts = Array.from(sharedState.contextMap.values())
+ result = await run(input, config)
- let context = allContexts[allContexts.length - 1]
+ let ruleCacheSize2 = context.ruleCache.size
- let ruleCacheSize1 = context.ruleCache.size
+ expect(result.css).toMatchFormattedCss(css`
+ .ml-2 {
+ margin-left: 0.5rem !important;
+ }
+ .ml-4 {
+ margin-left: 1rem !important;
+ }
+ .ml-6 {
+ margin-left: 1.5rem !important;
+ }
+ `)
- expect(result.css).toMatchFormattedCss(css`
- .ml-2 {
- margin-left: 0.5rem !important;
- }
- .ml-4 {
- margin-left: 1rem !important;
- }
- `)
-
- await fs.promises.writeFile(
- config.content[0],
- html`
-
-
- `
- )
-
- result = await run(input, config)
-
- let ruleCacheSize2 = context.ruleCache.size
-
- expect(result.css).toMatchFormattedCss(css`
- .ml-2 {
- margin-left: 0.5rem !important;
- }
- .ml-4 {
- margin-left: 1rem !important;
- }
- .ml-6 {
- margin-left: 1.5rem !important;
- }
- `)
-
- // The rule cache was effectively doubling in size previously
- // because the rule cache was never de-duped
- // This ensures this behavior doesn't return
- expect(ruleCacheSize2 - ruleCacheSize1).toBeLessThan(10)
- })
+ // The rule cache was effectively doubling in size previously
+ // because the rule cache was never de-duped
+ // This ensures this behavior doesn't return
+ expect(ruleCacheSize2 - ruleCacheSize1).toBeLessThan(10)
})
})
diff --git a/tests/important-modifier-prefix.test.js b/tests/important-modifier-prefix.test.js
index 782ec809e..2c6a8bb62 100644
--- a/tests/important-modifier-prefix.test.js
+++ b/tests/important-modifier-prefix.test.js
@@ -1,79 +1,77 @@
-import { crosscheck, run, html, css, defaults } from './util/run'
+import { run, html, css, defaults } from './util/run'
-crosscheck(() => {
- test('important modifier with prefix', () => {
- let config = {
- important: false,
- prefix: 'tw-',
- darkMode: 'class',
- content: [
- {
- raw: html`
-
-
-
-
-
-
`,
- },
- ],
- corePlugins: { preflight: false },
- }
+test('important modifier with prefix', () => {
+ let config = {
+ important: false,
+ prefix: 'tw-',
+ darkMode: 'class',
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults}
+ .\!tw-container {
+ width: 100% !important;
+ }
+ @media (min-width: 640px) {
.\!tw-container {
- width: 100% !important;
+ max-width: 640px !important;
}
- @media (min-width: 640px) {
- .\!tw-container {
- max-width: 640px !important;
- }
+ }
+ @media (min-width: 768px) {
+ .\!tw-container {
+ max-width: 768px !important;
}
- @media (min-width: 768px) {
- .\!tw-container {
- max-width: 768px !important;
- }
+ }
+ @media (min-width: 1024px) {
+ .\!tw-container {
+ max-width: 1024px !important;
}
- @media (min-width: 1024px) {
- .\!tw-container {
- max-width: 1024px !important;
- }
+ }
+ @media (min-width: 1280px) {
+ .\!tw-container {
+ max-width: 1280px !important;
}
- @media (min-width: 1280px) {
- .\!tw-container {
- max-width: 1280px !important;
- }
+ }
+ @media (min-width: 1536px) {
+ .\!tw-container {
+ max-width: 1536px !important;
}
- @media (min-width: 1536px) {
- .\!tw-container {
- max-width: 1536px !important;
- }
+ }
+ .\!tw-font-bold {
+ font-weight: 700 !important;
+ }
+ .hover\:\!tw-text-center:hover {
+ text-align: center !important;
+ }
+ @media (min-width: 1024px) {
+ .lg\:\!tw-opacity-50 {
+ opacity: 0.5 !important;
}
- .\!tw-font-bold {
- font-weight: 700 !important;
+ }
+ @media (min-width: 1280px) {
+ .xl\:focus\:disabled\:\!tw-float-right:disabled:focus {
+ float: right !important;
}
- .hover\:\!tw-text-center:hover {
- text-align: center !important;
- }
- @media (min-width: 1024px) {
- .lg\:\!tw-opacity-50 {
- opacity: 0.5 !important;
- }
- }
- @media (min-width: 1280px) {
- .xl\:focus\:disabled\:\!tw-float-right:disabled:focus {
- float: right !important;
- }
- }
- `)
- })
+ }
+ `)
})
})
diff --git a/tests/important-modifier.test.js b/tests/important-modifier.test.js
index 6d30fd0bc..fe4e14b33 100644
--- a/tests/important-modifier.test.js
+++ b/tests/important-modifier.test.js
@@ -1,153 +1,149 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(() => {
- test('important modifier', () => {
- let config = {
- important: false,
- darkMode: 'class',
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- function ({ theme, matchUtilities, addComponents }) {
- matchUtilities(
- {
- 'custom-parent': (value) => {
- return {
- '.custom-child': {
- margin: value,
- },
- }
- },
+test('important modifier', () => {
+ let config = {
+ important: false,
+ darkMode: 'class',
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ function ({ theme, matchUtilities, addComponents }) {
+ matchUtilities(
+ {
+ 'custom-parent': (value) => {
+ return {
+ '.custom-child': {
+ margin: value,
+ },
+ }
},
- { values: theme('spacing') }
- )
- addComponents({
- '.btn': {
- '&.disabled, &:disabled': {
- color: 'gray',
- },
+ },
+ { values: theme('spacing') }
+ )
+ addComponents({
+ '.btn': {
+ '&.disabled, &:disabled': {
+ color: 'gray',
},
- })
- },
- ],
- }
+ },
+ })
+ },
+ ],
+ }
- let input = css`
- @tailwind components;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind components;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .\!container {
+ width: 100% !important;
+ }
+ @media (min-width: 640px) {
.\!container {
- width: 100% !important;
+ max-width: 640px !important;
}
- @media (min-width: 640px) {
- .\!container {
- max-width: 640px !important;
- }
+ }
+ @media (min-width: 768px) {
+ .\!container {
+ max-width: 768px !important;
}
- @media (min-width: 768px) {
- .\!container {
- max-width: 768px !important;
- }
+ }
+ @media (min-width: 1024px) {
+ .\!container {
+ max-width: 1024px !important;
}
- @media (min-width: 1024px) {
- .\!container {
- max-width: 1024px !important;
- }
+ }
+ @media (min-width: 1280px) {
+ .\!container {
+ max-width: 1280px !important;
}
- @media (min-width: 1280px) {
- .\!container {
- max-width: 1280px !important;
- }
+ }
+ @media (min-width: 1536px) {
+ .\!container {
+ max-width: 1536px !important;
}
- @media (min-width: 1536px) {
- .\!container {
- max-width: 1536px !important;
- }
+ }
+ .btn.disabled,
+ .btn:disabled {
+ color: gray;
+ }
+ .btn.\!disabled {
+ color: gray !important;
+ }
+ .\!font-bold {
+ font-weight: 700 !important;
+ }
+ .\!custom-parent-5 .custom-child {
+ margin: 1.25rem !important;
+ }
+ .hover\:\!text-center:hover {
+ text-align: center !important;
+ }
+ @media (min-width: 1024px) {
+ .lg\:\!opacity-50 {
+ opacity: 0.5 !important;
}
- .btn.disabled,
- .btn:disabled {
- color: gray;
+ }
+ @media (min-width: 1280px) {
+ .xl\:focus\:disabled\:\!float-right:disabled:focus {
+ float: right !important;
}
- .btn.\!disabled {
- color: gray !important;
- }
- .\!font-bold {
- font-weight: 700 !important;
- }
- .\!custom-parent-5 .custom-child {
- margin: 1.25rem !important;
- }
- .hover\:\!text-center:hover {
- text-align: center !important;
- }
- @media (min-width: 1024px) {
- .lg\:\!opacity-50 {
- opacity: 0.5 !important;
- }
- }
- @media (min-width: 1280px) {
- .xl\:focus\:disabled\:\!float-right:disabled:focus {
- float: right !important;
- }
- }
- `)
- })
- })
-
- test('the important modifier works on utilities using :where()', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- function ({ addComponents }) {
- addComponents({
- ':where(.btn)': {
- backgroundColor: '#00f',
- },
- })
- },
- ],
- }
-
- let input = css`
- @tailwind components;
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- :where(.\!btn) {
- background-color: #00f !important;
- }
- :where(.btn) {
- background-color: #00f;
- }
- :where(.hover\:btn:hover) {
- background-color: #00f;
- }
- :where(.hover\:focus\:disabled\:\!btn:disabled:focus:hover) {
- background-color: #00f !important;
- }
- `)
- })
+ }
+ `)
+ })
+})
+
+test('the important modifier works on utilities using :where()', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ function ({ addComponents }) {
+ addComponents({
+ ':where(.btn)': {
+ backgroundColor: '#00f',
+ },
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind components;
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ :where(.\!btn) {
+ background-color: #00f !important;
+ }
+ :where(.btn),
+ :where(.hover\:btn:hover) {
+ background-color: #00f;
+ }
+ :where(.hover\:focus\:disabled\:\!btn:disabled:focus:hover) {
+ background-color: #00f !important;
+ }
+ `)
})
})
diff --git a/tests/important-selector.test.js b/tests/important-selector.test.js
index 22edafb94..1ec20922c 100644
--- a/tests/important-selector.test.js
+++ b/tests/important-selector.test.js
@@ -1,205 +1,202 @@
-import { crosscheck, run, html, css, defaults } from './util/run'
+import { run, html, css, defaults } from './util/run'
-crosscheck(({ stable, oxide }) => {
- test('important selector', () => {
- let config = {
- important: '#app',
- darkMode: 'class',
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- function ({ addComponents, addUtilities }) {
- addComponents(
- {
- '.btn': {
- button: 'yes',
- },
+test('important selector', () => {
+ let config = {
+ important: '#app',
+ darkMode: 'class',
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ function ({ addComponents, addUtilities }) {
+ addComponents(
+ {
+ '.btn': {
+ button: 'yes',
},
- { respectImportant: true }
- )
- addComponents(
- {
- '@font-face': {
- 'font-family': 'Inter',
- },
- '@page': {
- margin: '1cm',
- },
+ },
+ { respectImportant: true }
+ )
+ addComponents(
+ {
+ '@font-face': {
+ 'font-family': 'Inter',
},
- { respectImportant: true }
- )
- addUtilities(
- {
- '.custom-util': {
- button: 'no',
- },
+ '@page': {
+ margin: '1cm',
},
- { respectImportant: false }
- )
- },
- ],
+ },
+ { respectImportant: true }
+ )
+ addUtilities(
+ {
+ '.custom-util': {
+ button: 'no',
+ },
+ },
+ { respectImportant: false }
+ )
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind base;
+ @tailwind components;
+ @layer components {
+ .custom-component {
+ @apply font-bold;
+ }
+ .custom-important-component {
+ @apply text-center !important;
+ }
}
+ @tailwind utilities;
+ `
- let input = css`
- @tailwind base;
- @tailwind components;
- @layer components {
- .custom-component {
- @apply font-bold;
- }
- .custom-important-component {
- @apply text-center !important;
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults}
+ .container {
+ width: 100%;
+ }
+ @media (min-width: 640px) {
+ .container {
+ max-width: 640px;
}
}
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
+ @media (min-width: 768px) {
.container {
- width: 100%;
+ max-width: 768px;
}
- @media (min-width: 640px) {
- .container {
- max-width: 640px;
- }
+ }
+ @media (min-width: 1024px) {
+ .container {
+ max-width: 1024px;
}
- @media (min-width: 768px) {
- .container {
- max-width: 768px;
- }
+ }
+ @media (min-width: 1280px) {
+ .container {
+ max-width: 1280px;
}
- @media (min-width: 1024px) {
- .container {
- max-width: 1024px;
- }
+ }
+ @media (min-width: 1536px) {
+ .container {
+ max-width: 1536px;
}
- @media (min-width: 1280px) {
- .container {
- max-width: 1280px;
- }
+ }
+ #app .btn {
+ button: yes;
+ }
+ @font-face {
+ font-family: Inter;
+ }
+ @page {
+ margin: 1cm;
+ }
+ .custom-component {
+ font-weight: 700;
+ }
+ .custom-important-component {
+ text-align: center !important;
+ }
+ @keyframes spin {
+ to {
+ transform: rotate(360deg);
}
- @media (min-width: 1536px) {
- .container {
- max-width: 1536px;
- }
- }
- #app .btn {
- button: yes;
- }
- @font-face {
- font-family: Inter;
- }
- @page {
- margin: 1cm;
- }
- .custom-component {
- font-weight: 700;
- }
- .custom-important-component {
- text-align: center !important;
- }
- @keyframes spin {
- to {
- transform: rotate(360deg);
- }
- }
- #app :is(.animate-spin) {
- animation: 1s linear infinite spin;
- }
- #app :is(.font-bold) {
- font-weight: 700;
- }
- .custom-util {
- button: no;
- }
- #app :is(.group:hover .group-hover\:focus-within\:text-left:focus-within) {
- text-align: left;
- }
- #app :is(:is([dir='rtl'] .rtl\:active\:text-center:active)) {
+ }
+ #app .animate-spin {
+ animation: 1s linear infinite spin;
+ }
+ #app .font-bold {
+ font-weight: 700;
+ }
+ .custom-util {
+ button: no;
+ }
+ #app :is(.group:hover .group-hover\:focus-within\:text-left:focus-within) {
+ text-align: left;
+ }
+ #app :is([dir='rtl'] .rtl\:active\:text-center:active) {
+ text-align: center;
+ }
+ @media (prefers-reduced-motion: no-preference) {
+ #app .motion-safe\:hover\:text-center:hover {
text-align: center;
}
- @media (prefers-reduced-motion: no-preference) {
- #app :is(.motion-safe\:hover\:text-center:hover) {
- text-align: center;
- }
+ }
+ #app :is(.dark .dark\:before\:underline):before {
+ content: var(--tw-content);
+ text-decoration-line: underline;
+ }
+ #app :is(.dark .dark\:focus\:text-left:focus) {
+ text-align: left;
+ }
+ @media (min-width: 768px) {
+ #app .md\:hover\:text-right:hover {
+ text-align: right;
}
- #app :is(.dark .dark\:before\:underline):before {
- content: var(--tw-content);
- text-decoration-line: underline;
- }
- #app :is(:is(.dark .dark\:focus\:text-left:focus)) {
- text-align: left;
- }
- @media (min-width: 768px) {
- #app :is(.md\:hover\:text-right:hover) {
- text-align: right;
- }
- }
- #app
- :is(
- [dir='rtl'] :is(.dark .hover\:\[\&\:\:file-selector-button\]\:rtl\:dark\:bg-black\/100)
- )::file-selector-button:hover {
- background-color: #000;
- }
- `)
- })
- })
-
- test('pseudo-elements are appended after the `:is()`', () => {
- let config = {
- important: '#app',
- darkMode: 'class',
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- #app :is(.dark .dark\:before\:bg-black)::before {
- content: var(--tw-content);
- --tw-bg-opacity: 1;
- background-color: rgb(0 0 0 / var(--tw-bg-opacity));
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- #app :is(.dark .dark\:before\:bg-black)::before {
- content: var(--tw-content);
- background-color: #000;
- }
- `)
- })
+ }
+ #app
+ :-webkit-any(
+ [dir='rtl']
+ :-webkit-any(.dark .hover\:\[\&\:\:file-selector-button\]\:rtl\:dark\:bg-black\/100)
+ )::-webkit-file-upload-button:hover {
+ background-color: #000;
+ }
+ #app
+ :is(
+ [dir='rtl'] :is(.dark .hover\:\[\&\:\:file-selector-button\]\:rtl\:dark\:bg-black\/100)
+ )::file-selector-button:hover {
+ background-color: #000;
+ }
+ `)
+ })
+})
+
+test('pseudo-elements are appended after the `:-webkit-any()`', () => {
+ let config = {
+ important: '#app',
+ darkMode: 'class',
+ 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}
+ #app :is(.dark .dark\:before\:flex):before {
+ content: var(--tw-content);
+ display: flex;
+ }
+ `)
})
})
diff --git a/tests/kitchen-sink.test.js b/tests/kitchen-sink.test.js
index 3b02c8966..b025f8625 100644
--- a/tests/kitchen-sink.test.js
+++ b/tests/kitchen-sink.test.js
@@ -1,1374 +1,834 @@
-import { crosscheck, run, html, css, defaults } from './util/run'
+import { run, html, css, defaults } from './util/run'
-crosscheck(({ stable, oxide }) => {
- test('it works', () => {
- let config = {
- darkMode: 'class',
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+test('it works', () => {
+ let config = {
+ darkMode: 'class',
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
- `,
+ things: Array /* PropType */,
+ },
+ setup: () => {
+ const count = ref(0)
+ // Weird regex-looking stuff that once caused a stack overflow in candidatePermutations
+ const pattern = ' ]-[] '
+ return {
+ count,
+ stuff: [] /* string[] | undefined */,
+ }
+ },
+ })
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ extend: {
+ screens: {
+ range: { min: '1280px', max: '1535px' },
+ multi: [{ min: '640px', max: '767px' }, { max: '868px' }],
},
- ],
- corePlugins: { preflight: false },
- theme: {
- extend: {
- screens: {
- range: { min: '1280px', max: '1535px' },
- multi: [{ min: '640px', max: '767px' }, { max: '868px' }],
- },
- gradientColorStops: {
- foo: '#bada55',
- },
- backgroundImage: {
- 'hero--home-1': "url('/images/homepage-1.jpg')",
- },
+ gradientColorStops: {
+ foo: '#bada55',
+ },
+ backgroundImage: {
+ 'hero--home-1': "url('/images/homepage-1.jpg')",
},
},
- plugins: [
- function ({ addVariant }) {
- addVariant(
- 'foo',
- ({ container }) => {
- container.walkRules((rule) => {
- rule.selector = `.foo\\:${rule.selector.slice(1)}`
- rule.walkDecls((decl) => {
- decl.important = true
- })
+ },
+ plugins: [
+ function ({ addVariant }) {
+ addVariant(
+ 'foo',
+ ({ container }) => {
+ container.walkRules((rule) => {
+ rule.selector = `.foo\\:${rule.selector.slice(1)}`
+ rule.walkDecls((decl) => {
+ decl.important = true
})
+ })
+ },
+ { before: 'sm' }
+ )
+ },
+ function ({ addUtilities, addBase, theme }) {
+ addBase({
+ h1: {
+ fontSize: theme('fontSize.2xl'),
+ fontWeight: theme('fontWeight.bold'),
+ '&:first-child': {
+ marginTop: '0px',
},
- { before: 'sm' }
- )
- },
- function ({ addUtilities, addBase, theme }) {
- addBase({
- h1: {
- fontSize: theme('fontSize.2xl'),
- fontWeight: theme('fontWeight.bold'),
- '&:first-child': {
- marginTop: '0px',
- },
+ },
+ })
+ addUtilities(
+ {
+ '.magic-none': {
+ magic: 'none',
},
- })
- addUtilities(
- {
- '.magic-none': {
- magic: 'none',
- },
- '.magic-tons': {
- magic: 'tons',
- },
+ '.magic-tons': {
+ magic: 'tons',
},
- ['responsive', 'hover']
- )
- },
- ],
- }
+ },
+ ['responsive', 'hover']
+ )
+ },
+ ],
+ }
- let input = css`
- @layer utilities {
- .custom-util {
- background: #abcdef;
- }
- *,
- ::before,
- ::after,
- ::backdrop {
- margin: 10px;
- }
+ let input = css`
+ @layer utilities {
+ .custom-util {
+ background: #abcdef;
}
- @layer components {
- .test-apply-font-variant {
- @apply ordinal tabular-nums;
- }
- .custom-component {
- background: #123456;
- }
- *,
- ::before,
- ::after,
- ::backdrop {
- padding: 5px;
- }
- .foo .bg-black {
- appearance: none;
- }
+ *,
+ ::before,
+ ::after,
+ ::backdrop {
+ margin: 10px;
}
- @layer base {
- div {
- background: #654321;
- }
+ }
+ @layer components {
+ .test-apply-font-variant {
+ @apply ordinal tabular-nums;
}
+ .custom-component {
+ background: #123456;
+ }
+ *,
+ ::before,
+ ::after,
+ ::backdrop {
+ padding: 5px;
+ }
+ .foo .bg-black {
+ appearance: none;
+ }
+ }
+ @layer base {
+ div {
+ background: #654321;
+ }
+ }
+ .theme-test {
+ font-family: theme('fontFamily.sans');
+ color: theme('colors.blue.500');
+ }
+ @screen lg {
+ .screen-test {
+ color: purple;
+ }
+ }
+ .apply-1 {
+ @apply mt-6;
+ }
+ .apply-2 {
+ @apply mt-6;
+ }
+ .apply-test {
+ @apply mt-6 bg-pink-500 hover:font-bold focus:hover:font-bold sm:bg-green-500 sm:focus:even:bg-pink-200;
+ }
+ .apply-components {
+ @apply container mx-auto;
+ }
+ .drop-empty-rules {
+ @apply hover:font-bold;
+ }
+ .apply-group {
+ @apply group-hover:font-bold;
+ }
+ .apply-dark-mode {
+ @apply dark:font-bold;
+ }
+ .apply-with-existing:hover {
+ @apply font-normal sm:bg-green-500;
+ }
+ .multiple,
+ .selectors {
+ @apply font-bold group-hover:font-normal;
+ }
+ .list {
+ @apply space-y-4;
+ }
+ .nested {
+ .example {
+ @apply font-bold hover:font-normal;
+ }
+ }
+ .apply-order-a {
+ @apply m-5 mt-6;
+ }
+ .apply-order-b {
+ @apply m-5 mt-6;
+ }
+ .apply-dark-group-example-a {
+ @apply dark:group-hover:bg-green-500;
+ }
+ .crazy-example {
+ @apply sm:motion-safe:group-active:focus:opacity-10;
+ }
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
.theme-test {
- font-family: theme('fontFamily.sans');
- color: theme('colors.blue.500');
+ color: #3b82f6;
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
+ Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji,
+ Segoe UI Symbol, Noto Color Emoji;
}
- @screen lg {
+ @media (min-width: 1024px) {
.screen-test {
color: purple;
}
}
- .apply-1 {
- @apply mt-6;
- }
+ .apply-1,
.apply-2 {
- @apply mt-6;
+ margin-top: 1.5rem;
}
.apply-test {
- @apply mt-6 bg-pink-500 hover:font-bold focus:hover:font-bold sm:bg-green-500 sm:focus:even:bg-pink-200;
+ --tw-bg-opacity: 1;
+ background-color: rgb(236 72 153 / var(--tw-bg-opacity));
+ margin-top: 1.5rem;
+ }
+ .apply-test:hover,
+ .apply-test:hover:focus {
+ font-weight: 700;
+ }
+ @media (min-width: 640px) {
+ .apply-test {
+ --tw-bg-opacity: 1;
+ background-color: rgb(34 197 94 / var(--tw-bg-opacity));
+ }
+ .apply-test:nth-child(2n):focus {
+ --tw-bg-opacity: 1;
+ background-color: rgb(251 207 232 / var(--tw-bg-opacity));
+ }
}
.apply-components {
- @apply container mx-auto;
+ width: 100%;
}
- .drop-empty-rules {
- @apply hover:font-bold;
+ @media (min-width: 640px) {
+ .apply-components {
+ max-width: 640px;
+ }
}
- .apply-group {
- @apply group-hover:font-bold;
+ @media (min-width: 768px) {
+ .apply-components {
+ max-width: 768px;
+ }
}
- .apply-dark-mode {
- @apply dark:font-bold;
+ @media (min-width: 1024px) {
+ .apply-components {
+ max-width: 1024px;
+ }
+ }
+ @media (min-width: 1280px) {
+ .apply-components {
+ max-width: 1280px;
+ }
+ }
+ @media (min-width: 1536px) {
+ .apply-components {
+ max-width: 1536px;
+ }
+ }
+ .apply-components {
+ margin-left: auto;
+ margin-right: auto;
+ }
+ .drop-empty-rules:hover,
+ .group:hover .apply-group,
+ :is(.dark .apply-dark-mode) {
+ font-weight: 700;
}
.apply-with-existing:hover {
- @apply font-normal sm:bg-green-500;
+ font-weight: 400;
+ }
+ @media (min-width: 640px) {
+ .apply-with-existing:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(34 197 94 / var(--tw-bg-opacity));
+ }
}
.multiple,
.selectors {
- @apply font-bold group-hover:font-normal;
+ font-weight: 700;
}
- .list {
- @apply space-y-4;
+ .group:hover .multiple,
+ .group:hover .selectors {
+ font-weight: 400;
}
- .nested {
- .example {
- @apply font-bold hover:font-normal;
- }
+ .list > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-y-reverse: 0;
+ margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
+ margin-bottom: calc(1rem * var(--tw-space-y-reverse));
}
- .apply-order-a {
- @apply m-5 mt-6;
+ .nested .example {
+ font-weight: 700;
}
+ .nested .example:hover {
+ font-weight: 400;
+ }
+ .apply-order-a,
.apply-order-b {
- @apply m-5 mt-6;
+ margin: 1.5rem 1.25rem 1.25rem;
}
- .apply-dark-group-example-a {
- @apply dark:group-hover:bg-green-500;
+ :is(.dark .group:hover .apply-dark-group-example-a) {
+ --tw-bg-opacity: 1;
+ background-color: rgb(34 197 94 / var(--tw-bg-opacity));
}
- .crazy-example {
- @apply sm:motion-safe:group-active:focus:opacity-10;
+ @media (min-width: 640px) {
+ @media (prefers-reduced-motion: no-preference) {
+ .group:active .crazy-example:focus {
+ opacity: 0.1;
+ }
+ }
}
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .theme-test {
- color: #3b82f6;
- font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
- Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji,
- Segoe UI Symbol, Noto Color Emoji;
- }
- @media (min-width: 1024px) {
- .screen-test {
- color: purple;
- }
- }
- .apply-1,
- .apply-2 {
- margin-top: 1.5rem;
- }
- .apply-test {
- --tw-bg-opacity: 1;
- background-color: rgb(236 72 153 / var(--tw-bg-opacity));
- margin-top: 1.5rem;
- }
- .apply-test:hover,
- .apply-test:hover:focus {
- font-weight: 700;
- }
- @media (min-width: 640px) {
- .apply-test {
- --tw-bg-opacity: 1;
- background-color: rgb(34 197 94 / var(--tw-bg-opacity));
- }
- .apply-test:nth-child(2n):focus {
- --tw-bg-opacity: 1;
- background-color: rgb(251 207 232 / var(--tw-bg-opacity));
- }
- }
- .apply-components {
- width: 100%;
- }
- @media (min-width: 640px) {
- .apply-components {
- max-width: 640px;
- }
- }
- @media (min-width: 768px) {
- .apply-components {
- max-width: 768px;
- }
- }
- @media (min-width: 1024px) {
- .apply-components {
- max-width: 1024px;
- }
- }
- @media (min-width: 1280px) {
- .apply-components {
- max-width: 1280px;
- }
- }
- @media (min-width: 1536px) {
- .apply-components {
- max-width: 1536px;
- }
- }
- .apply-components {
- margin-left: auto;
- margin-right: auto;
- }
- .drop-empty-rules:hover,
- .group:hover .apply-group,
- :is(.dark .apply-dark-mode) {
- font-weight: 700;
- }
- .apply-with-existing:hover {
- font-weight: 400;
- }
- @media (min-width: 640px) {
- .apply-with-existing:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(34 197 94 / var(--tw-bg-opacity));
- }
- }
- .multiple,
- .selectors {
- font-weight: 700;
- }
- .group:hover .multiple,
- .group:hover .selectors {
- font-weight: 400;
- }
- .list > :not([hidden]) ~ :not([hidden]) {
- --tw-space-y-reverse: 0;
- margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
- margin-bottom: calc(1rem * var(--tw-space-y-reverse));
- }
- .nested .example {
- font-weight: 700;
- }
- .nested .example:hover {
- font-weight: 400;
- }
- .apply-order-a,
- .apply-order-b {
- margin: 1.5rem 1.25rem 1.25rem;
- }
- :is(.dark .group:hover .apply-dark-group-example-a) {
- --tw-bg-opacity: 1;
- background-color: rgb(34 197 94 / var(--tw-bg-opacity));
- }
- @media (min-width: 640px) {
- @media (prefers-reduced-motion: no-preference) {
- .group:active .crazy-example:focus {
- opacity: 0.1;
- }
- }
- }
- h1 {
- font-size: 1.5rem;
- font-weight: 700;
- }
- h1:first-child {
- margin-top: 0;
- }
- div {
- background: #654321;
- }
- ${defaults}
+ h1 {
+ font-size: 1.5rem;
+ font-weight: 700;
+ }
+ h1:first-child {
+ margin-top: 0;
+ }
+ div {
+ background: #654321;
+ }
+ ${defaults}
+ .container {
+ width: 100%;
+ }
+ @media (min-width: 640px) {
.container {
+ max-width: 640px;
+ }
+ }
+ @media (min-width: 768px) {
+ .container {
+ max-width: 768px;
+ }
+ }
+ @media (min-width: 1024px) {
+ .container {
+ max-width: 1024px;
+ }
+ }
+ @media (min-width: 1280px) {
+ .container {
+ max-width: 1280px;
+ }
+ }
+ @media (min-width: 1536px) {
+ .container {
+ max-width: 1536px;
+ }
+ }
+ .test-apply-font-variant {
+ --tw-ordinal: ordinal;
+ --tw-numeric-spacing: tabular-nums;
+ font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
+ var(--tw-numeric-spacing) var(--tw-numeric-fraction);
+ }
+ .custom-component {
+ background: #123456;
+ }
+ *,
+ :before,
+ :after {
+ padding: 5px;
+ }
+ *,
+ :before,
+ :after {
+ padding: 5px;
+ }
+ ::-webkit-backdrop {
+ padding: 5px;
+ }
+ ::backdrop {
+ padding: 5px;
+ }
+ .foo .bg-black {
+ -webkit-appearance: none;
+ appearance: none;
+ }
+ .inset-6 {
+ inset: 1.5rem;
+ }
+ .inset-x-1 {
+ left: 0.25rem;
+ right: 0.25rem;
+ }
+ .end-8 {
+ inset-inline-end: 2rem;
+ }
+ .start-4 {
+ inset-inline-start: 1rem;
+ }
+ .mx-1 {
+ margin-left: 0.25rem;
+ margin-right: 0.25rem;
+ }
+ .me-8 {
+ margin-inline-end: 2rem;
+ }
+ .ms-4 {
+ margin-inline-start: 1rem;
+ }
+ .mt-6 {
+ margin-top: 1.5rem;
+ }
+ .line-clamp-2 {
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ display: -webkit-box;
+ overflow: hidden;
+ }
+ .line-clamp-\[33\] {
+ -webkit-line-clamp: 33;
+ -webkit-box-orient: vertical;
+ display: -webkit-box;
+ overflow: hidden;
+ }
+ .line-clamp-\[var\(--line-clamp-variable\)\] {
+ -webkit-line-clamp: var(--line-clamp-variable);
+ -webkit-box-orient: vertical;
+ display: -webkit-box;
+ overflow: hidden;
+ }
+ .line-clamp-none {
+ -webkit-line-clamp: none;
+ -webkit-box-orient: horizontal;
+ display: block;
+ overflow: visible;
+ }
+ .scale-50 {
+ --tw-scale-x: 0.5;
+ --tw-scale-y: 0.5;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ .transform {
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ .grid-cols-\[200px\,repeat\(auto-fill\,minmax\(15\%\,100px\)\)\,300px\] {
+ grid-template-columns: 200px repeat(auto-fill, minmax(15%, 100px)) 300px;
+ }
+ .rounded-e {
+ border-start-end-radius: 0.25rem;
+ border-end-end-radius: 0.25rem;
+ }
+ .rounded-s {
+ border-start-start-radius: 0.25rem;
+ border-end-start-radius: 0.25rem;
+ }
+ .rounded-es {
+ border-end-start-radius: 0.25rem;
+ }
+ .rounded-ss {
+ border-start-start-radius: 0.25rem;
+ }
+ .border-2 {
+ border-width: 2px;
+ }
+ .border-e-4 {
+ border-inline-end-width: 4px;
+ }
+ .border-s-0 {
+ border-inline-start-width: 0;
+ }
+ .border-black {
+ --tw-border-opacity: 1;
+ border-color: rgb(0 0 0 / var(--tw-border-opacity));
+ }
+ .border-e-red-400 {
+ --tw-border-opacity: 1;
+ border-inline-end-color: rgb(248 113 113 / var(--tw-border-opacity));
+ }
+ .border-s-green-500 {
+ --tw-border-opacity: 1;
+ border-inline-start-color: rgb(34 197 94 / var(--tw-border-opacity));
+ }
+ .bg-black {
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 0 0 / var(--tw-bg-opacity));
+ }
+ .bg-green-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(34 197 94 / var(--tw-bg-opacity));
+ }
+ .bg-opacity-50 {
+ --tw-bg-opacity: 0.5;
+ }
+ .bg-gradient-to-r {
+ background-image: linear-gradient(to right, var(--tw-gradient-stops));
+ }
+ .bg-hero--home-1 {
+ background-image: url('/images/homepage-1.jpg');
+ }
+ .from-foo {
+ --tw-gradient-from: #bada55 var(--tw-gradient-from-position);
+ --tw-gradient-to: #bada5500 var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+ }
+ .px-1 {
+ padding-left: 0.25rem;
+ padding-right: 0.25rem;
+ }
+ .pe-8 {
+ padding-inline-end: 2rem;
+ }
+ .ps-4 {
+ padding-inline-start: 1rem;
+ }
+ .pt-6 {
+ padding-top: 1.5rem;
+ }
+ .text-center {
+ text-align: center;
+ }
+ .font-medium {
+ font-weight: 500;
+ }
+ .shadow-md {
+ --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
+ --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
+ 0 2px 4px -2px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
+ var(--tw-shadow);
+ }
+ .shadow-sm {
+ --tw-shadow: 0 1px 2px 0 #0000000d;
+ --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
+ var(--tw-shadow);
+ }
+ .magic-none {
+ magic: none;
+ }
+ .magic-tons {
+ magic: tons;
+ }
+ .custom-util {
+ background: #abcdef;
+ }
+ *,
+ :before,
+ :after {
+ margin: 10px;
+ }
+ *,
+ :before,
+ :after {
+ margin: 10px;
+ }
+ ::-webkit-backdrop {
+ margin: 10px;
+ }
+ ::backdrop {
+ margin: 10px;
+ }
+ .hover\:container:hover {
+ width: 100%;
+ }
+ @media (min-width: 640px) {
+ .hover\:container:hover {
+ max-width: 640px;
+ }
+ }
+ @media (min-width: 768px) {
+ .hover\:container:hover {
+ max-width: 768px;
+ }
+ }
+ @media (min-width: 1024px) {
+ .hover\:container:hover {
+ max-width: 1024px;
+ }
+ }
+ @media (min-width: 1280px) {
+ .hover\:container:hover {
+ max-width: 1280px;
+ }
+ }
+ @media (min-width: 1536px) {
+ .hover\:container:hover {
+ max-width: 1536px;
+ }
+ }
+ @media (min-width: 640px) {
+ .sm\:container {
width: 100%;
}
@media (min-width: 640px) {
- .container {
+ .sm\:container {
max-width: 640px;
}
}
@media (min-width: 768px) {
- .container {
+ .sm\:container {
max-width: 768px;
}
}
@media (min-width: 1024px) {
- .container {
+ .sm\:container {
max-width: 1024px;
}
}
@media (min-width: 1280px) {
- .container {
+ .sm\:container {
max-width: 1280px;
}
}
@media (min-width: 1536px) {
- .container {
+ .sm\:container {
max-width: 1536px;
}
}
- .test-apply-font-variant {
- --tw-ordinal: ordinal;
+ }
+ @media (min-width: 768px) {
+ .md\:container {
+ width: 100%;
+ }
+ @media (min-width: 640px) {
+ .md\:container {
+ max-width: 640px;
+ }
+ }
+ @media (min-width: 768px) {
+ .md\:container {
+ max-width: 768px;
+ }
+ }
+ @media (min-width: 1024px) {
+ .md\:container {
+ max-width: 1024px;
+ }
+ }
+ @media (min-width: 1280px) {
+ .md\:container {
+ max-width: 1280px;
+ }
+ }
+ @media (min-width: 1536px) {
+ .md\:container {
+ max-width: 1536px;
+ }
+ }
+ }
+ .first\:pt-0:first-child {
+ padding-top: 0;
+ }
+ .hover\:scale-75:hover {
+ --tw-scale-x: 0.75;
+ --tw-scale-y: 0.75;
+ transform: translate(var(--tw-translate-x), 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\:font-bold:hover {
+ font-weight: 700;
+ }
+ .hover\:shadow-lg:hover {
+ --tw-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;
+ --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color),
+ 0 4px 6px -4px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
+ var(--tw-shadow);
+ }
+ .hover\:custom-util:hover {
+ background: #abcdef;
+ }
+ .focus\:font-normal:focus {
+ font-weight: 400;
+ }
+ .focus\:ring-2:focus {
+ --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(2px + 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);
+ }
+ .focus\:ring-blue-500:focus {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity));
+ }
+ .focus\:hover\:font-light:hover:focus {
+ font-weight: 300;
+ }
+ .disabled\:font-bold:disabled {
+ font-weight: 700;
+ }
+ .group:hover .group-hover\:opacity-100 {
+ opacity: 1;
+ }
+ .group:hover .group-hover\:custom-util {
+ background: #abcdef;
+ }
+ .group:active .group-active\:opacity-10 {
+ opacity: 0.1;
+ }
+ .foo\:custom-util,
+ .foo\:hover\:custom-util:hover {
+ background: #abcdef !important;
+ }
+ @media (prefers-reduced-motion: no-preference) {
+ .motion-safe\:transition {
+ transition-property: color, background-color, border-color, text-decoration-color, fill,
+ stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter, backdrop-filter;
+ transition-duration: 0.15s;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ }
+ .motion-safe\:custom-util {
+ background: #abcdef;
+ }
+ }
+ @media (prefers-reduced-motion: reduce) {
+ .motion-reduce\:transition {
+ transition-property: color, background-color, border-color, text-decoration-color, fill,
+ stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter, backdrop-filter;
+ transition-duration: 0.15s;
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ }
+ }
+ :is(.dark .dark\:custom-util) {
+ background: #abcdef;
+ }
+ @media (min-width: 640px) {
+ .sm\:text-center {
+ text-align: center;
+ }
+ .sm\:tabular-nums {
--tw-numeric-spacing: tabular-nums;
font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
var(--tw-numeric-spacing) var(--tw-numeric-fraction);
}
- .custom-component {
- background: #123456;
+ .sm\:custom-util {
+ background: #abcdef;
}
- *,
- :before,
- :after,
- ::backdrop {
- padding: 5px;
+ @media (prefers-reduced-motion: no-preference) {
+ .group:active .sm\:motion-safe\:group-active\:focus\:opacity-10:focus {
+ opacity: 0.1;
+ }
}
- .foo .bg-black {
- appearance: none;
- }
- .inset-6 {
- inset: 1.5rem;
- }
- .inset-x-1 {
- left: 0.25rem;
- right: 0.25rem;
- }
- .end-8 {
- inset-inline-end: 2rem;
- }
- .start-4 {
- inset-inline-start: 1rem;
- }
- .mx-1 {
- margin-left: 0.25rem;
- margin-right: 0.25rem;
- }
- .me-8 {
- margin-inline-end: 2rem;
- }
- .ms-4 {
- margin-inline-start: 1rem;
- }
- .mt-6 {
- margin-top: 1.5rem;
- }
- .line-clamp-2 {
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- display: -webkit-box;
- overflow: hidden;
- }
- .line-clamp-\[33\] {
- -webkit-line-clamp: 33;
- -webkit-box-orient: vertical;
- display: -webkit-box;
- overflow: hidden;
- }
- .line-clamp-\[var\(--line-clamp-variable\)\] {
- -webkit-line-clamp: var(--line-clamp-variable);
- -webkit-box-orient: vertical;
- display: -webkit-box;
- overflow: hidden;
- }
- .line-clamp-none {
- -webkit-line-clamp: none;
- -webkit-box-orient: horizontal;
- display: block;
- overflow: visible;
- }
- .scale-50 {
- --tw-scale-x: 0.5;
- --tw-scale-y: 0.5;
- transform: translate(var(--tw-translate-x), 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));
- }
- .transform {
- transform: translate(var(--tw-translate-x), 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));
- }
- .grid-cols-\[200px\,repeat\(auto-fill\,minmax\(15\%\,100px\)\)\,300px\] {
- grid-template-columns: 200px repeat(auto-fill, minmax(15%, 100px)) 300px;
- }
- .rounded-e {
- border-start-end-radius: 0.25rem;
- border-end-end-radius: 0.25rem;
- }
- .rounded-s {
- border-start-start-radius: 0.25rem;
- border-end-start-radius: 0.25rem;
- }
- .rounded-es {
- border-end-start-radius: 0.25rem;
- }
- .rounded-ss {
- border-start-start-radius: 0.25rem;
- }
- .border-2 {
- border-width: 2px;
- }
- .border-e-4 {
- border-inline-end-width: 4px;
- }
- .border-s-0 {
- border-inline-start-width: 0;
- }
- .border-black {
- --tw-border-opacity: 1;
- border-color: rgb(0 0 0 / var(--tw-border-opacity));
- }
- .border-e-red-400 {
- --tw-border-opacity: 1;
- border-inline-end-color: rgb(248 113 113 / var(--tw-border-opacity));
- }
- .border-s-green-500 {
- --tw-border-opacity: 1;
- border-inline-start-color: rgb(34 197 94 / var(--tw-border-opacity));
- }
- .bg-black {
- --tw-bg-opacity: 1;
- background-color: rgb(0 0 0 / var(--tw-bg-opacity));
- }
- .bg-green-500 {
- --tw-bg-opacity: 1;
- background-color: rgb(34 197 94 / var(--tw-bg-opacity));
- }
- .bg-opacity-50 {
- --tw-bg-opacity: 0.5;
- }
- .bg-gradient-to-r {
- background-image: linear-gradient(to right, var(--tw-gradient-stops));
- }
- .bg-hero--home-1 {
- background-image: url('/images/homepage-1.jpg');
- }
- .from-foo {
- --tw-gradient-from: #bada55 var(--tw-gradient-from-position);
- --tw-gradient-to: #bada5500 var(--tw-gradient-to-position);
- --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
- }
- .px-1 {
- padding-left: 0.25rem;
- padding-right: 0.25rem;
- }
- .pe-8 {
- padding-inline-end: 2rem;
- }
- .ps-4 {
- padding-inline-start: 1rem;
- }
- .pt-6 {
- padding-top: 1.5rem;
- }
- .text-center {
+ }
+ @media (min-width: 768px) {
+ .md\:text-center {
text-align: center;
}
- .font-medium {
- font-weight: 500;
+ .md\:opacity-50 {
+ opacity: 0.5;
}
- .shadow-md {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
- 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
- .shadow-sm {
+ .md\:shadow-sm {
--tw-shadow: 0 1px 2px 0 #0000000d;
--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
var(--tw-shadow);
}
- .magic-none {
- magic: none;
+ .md\:hover\:border-r-blue-500\/30:hover {
+ border-right-color: #3b82f64d;
}
- .magic-tons {
- magic: tons;
- }
- .custom-util {
- background: #abcdef;
- }
- *,
- :before,
- :after,
- ::backdrop {
- margin: 10px;
- }
- .hover\:container:hover {
- width: 100%;
- }
- @media (min-width: 640px) {
- .hover\:container:hover {
- max-width: 640px;
- }
- }
- @media (min-width: 768px) {
- .hover\:container:hover {
- max-width: 768px;
- }
- }
- @media (min-width: 1024px) {
- .hover\:container:hover {
- max-width: 1024px;
- }
- }
- @media (min-width: 1280px) {
- .hover\:container:hover {
- max-width: 1280px;
- }
- }
- @media (min-width: 1536px) {
- .hover\:container:hover {
- max-width: 1536px;
- }
- }
- @media (min-width: 640px) {
- .sm\:container {
- width: 100%;
- }
- @media (min-width: 640px) {
- .sm\:container {
- max-width: 640px;
- }
- }
- @media (min-width: 768px) {
- .sm\:container {
- max-width: 768px;
- }
- }
- @media (min-width: 1024px) {
- .sm\:container {
- max-width: 1024px;
- }
- }
- @media (min-width: 1280px) {
- .sm\:container {
- max-width: 1280px;
- }
- }
- @media (min-width: 1536px) {
- .sm\:container {
- max-width: 1536px;
- }
- }
- }
- @media (min-width: 768px) {
- .md\:container {
- width: 100%;
- }
- @media (min-width: 640px) {
- .md\:container {
- max-width: 640px;
- }
- }
- @media (min-width: 768px) {
- .md\:container {
- max-width: 768px;
- }
- }
- @media (min-width: 1024px) {
- .md\:container {
- max-width: 1024px;
- }
- }
- @media (min-width: 1280px) {
- .md\:container {
- max-width: 1280px;
- }
- }
- @media (min-width: 1536px) {
- .md\:container {
- max-width: 1536px;
- }
- }
- }
- .first\:pt-0:first-child {
- padding-top: 0;
- }
- .hover\:scale-75:hover {
- --tw-scale-x: 0.75;
- --tw-scale-y: 0.75;
- transform: translate(var(--tw-translate-x), 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\:font-bold:hover {
- font-weight: 700;
- }
- .hover\:shadow-lg:hover {
- --tw-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;
- --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color),
- 0 4px 6px -4px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
- .hover\:custom-util:hover {
- background: #abcdef;
- }
- .focus\:font-normal:focus {
- font-weight: 400;
- }
- .focus\:ring-2:focus {
- --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(2px + 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);
- }
- .focus\:ring-blue-500:focus {
- --tw-ring-opacity: 1;
- --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity));
- }
- .focus\:hover\:font-light:hover:focus {
- font-weight: 300;
- }
- .disabled\:font-bold:disabled {
- font-weight: 700;
- }
- .group:hover .group-hover\:opacity-100 {
- opacity: 1;
- }
- .group:hover .group-hover\:custom-util {
- background: #abcdef;
- }
- .group:active .group-active\:opacity-10 {
- opacity: 0.1;
- }
- .foo\:custom-util,
- .foo\:hover\:custom-util:hover {
- background: #abcdef !important;
+ .md\:hover\:opacity-20:hover {
+ opacity: 0.2;
}
@media (prefers-reduced-motion: no-preference) {
- .motion-safe\:transition {
+ .md\:motion-safe\:hover\:transition:hover {
transition-property: color, background-color, border-color, text-decoration-color, fill,
- stroke, opacity, box-shadow, transform, filter, backdrop-filter;
+ stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter,
+ backdrop-filter;
transition-duration: 0.15s;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
- .motion-safe\:custom-util {
- background: #abcdef;
+ :is(.dark .md\:dark\:motion-safe\:foo\:active\:custom-util:active) {
+ background: #abcdef !important;
}
}
- @media (prefers-reduced-motion: reduce) {
- .motion-reduce\:transition {
- transition-property: color, background-color, border-color, text-decoration-color, fill,
- stroke, opacity, box-shadow, transform, filter, backdrop-filter;
- transition-duration: 0.15s;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- }
- }
- :is(.dark .dark\:custom-util) {
- background: #abcdef;
- }
@media (min-width: 640px) {
- .sm\:text-center {
+ .md\:sm\:text-center {
text-align: center;
}
- .sm\:tabular-nums {
- --tw-numeric-spacing: tabular-nums;
- font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
- var(--tw-numeric-spacing) var(--tw-numeric-fraction);
- }
- .sm\:custom-util {
- background: #abcdef;
- }
- @media (prefers-reduced-motion: no-preference) {
- .group:active .sm\:motion-safe\:group-active\:focus\:opacity-10:focus {
- opacity: 0.1;
- }
- }
}
- @media (min-width: 768px) {
- .md\:text-center {
- text-align: center;
- }
- .md\:opacity-50 {
- opacity: 0.5;
- }
- .md\:shadow-sm {
- --tw-shadow: 0 1px 2px 0 #0000000d;
- --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
- .md\:hover\:border-r-blue-500\/30:hover {
- border-right-color: #3b82f64d;
- }
- .md\:hover\:opacity-20:hover {
- opacity: 0.2;
- }
- @media (prefers-reduced-motion: no-preference) {
- .md\:motion-safe\:hover\:transition:hover {
- transition-property: color, background-color, border-color, text-decoration-color,
- fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
- transition-duration: 0.15s;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- }
- :is(.dark .md\:dark\:motion-safe\:foo\:active\:custom-util:active) {
- background: #abcdef !important;
- }
- }
- @media (min-width: 640px) {
- .md\:sm\:text-center {
- text-align: center;
- }
- }
+ }
+ @media (min-width: 1280px) and (max-width: 1535px) {
+ .range\:text-right {
+ text-align: right;
}
- @media (min-width: 1280px) and (max-width: 1535px) {
- .range\:text-right {
- text-align: right;
- }
+ }
+ @media (min-width: 640px) and (max-width: 767px), (max-width: 868px) {
+ .multi\:text-left {
+ text-align: left;
}
- @media (min-width: 640px) and (max-width: 767px), (max-width: 868px) {
- .multi\:text-left {
- text-align: left;
- }
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .theme-test {
- color: #3b82f6;
- font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
- Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji,
- Segoe UI Symbol, Noto Color Emoji;
- }
- @media (min-width: 1024px) {
- .screen-test {
- color: purple;
- }
- }
- .apply-1,
- .apply-2 {
- margin-top: 1.5rem;
- }
- .apply-test {
- background-color: #ec4899;
- margin-top: 1.5rem;
- }
- .apply-test:hover,
- .apply-test:hover:focus {
- font-weight: 700;
- }
- @media (min-width: 640px) {
- .apply-test {
- background-color: #22c55e;
- }
- .apply-test:nth-child(2n):focus {
- background-color: #fbcfe8;
- }
- }
- .apply-components {
- width: 100%;
- }
- @media (min-width: 640px) {
- .apply-components {
- max-width: 640px;
- }
- }
- @media (min-width: 768px) {
- .apply-components {
- max-width: 768px;
- }
- }
- @media (min-width: 1024px) {
- .apply-components {
- max-width: 1024px;
- }
- }
- @media (min-width: 1280px) {
- .apply-components {
- max-width: 1280px;
- }
- }
- @media (min-width: 1536px) {
- .apply-components {
- max-width: 1536px;
- }
- }
- .apply-components {
- margin-left: auto;
- margin-right: auto;
- }
- .drop-empty-rules:hover,
- .group:hover .apply-group,
- :is(.dark .apply-dark-mode) {
- font-weight: 700;
- }
- .apply-with-existing:hover {
- font-weight: 400;
- }
- @media (min-width: 640px) {
- .apply-with-existing:hover {
- background-color: #22c55e;
- }
- }
- .multiple,
- .selectors {
- font-weight: 700;
- }
- .group:hover .multiple,
- .group:hover .selectors {
- font-weight: 400;
- }
- .list > :not([hidden]) ~ :not([hidden]) {
- --tw-space-y-reverse: 0;
- margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
- margin-bottom: calc(1rem * var(--tw-space-y-reverse));
- }
- .nested .example {
- font-weight: 700;
- }
- .nested .example:hover {
- font-weight: 400;
- }
- .apply-order-a,
- .apply-order-b {
- margin: 1.5rem 1.25rem 1.25rem;
- }
- :is(.dark .group:hover .apply-dark-group-example-a) {
- background-color: #22c55e;
- }
- @media (min-width: 640px) {
- @media (prefers-reduced-motion: no-preference) {
- .group:active .crazy-example:focus {
- opacity: 0.1;
- }
- }
- }
- h1 {
- font-size: 1.5rem;
- font-weight: 700;
- }
- h1:first-child {
- margin-top: 0;
- }
- div {
- background: #654321;
- }
- ${defaults}
- .container {
- width: 100%;
- }
- @media (min-width: 640px) {
- .container {
- max-width: 640px;
- }
- }
- @media (min-width: 768px) {
- .container {
- max-width: 768px;
- }
- }
- @media (min-width: 1024px) {
- .container {
- max-width: 1024px;
- }
- }
- @media (min-width: 1280px) {
- .container {
- max-width: 1280px;
- }
- }
- @media (min-width: 1536px) {
- .container {
- max-width: 1536px;
- }
- }
- .test-apply-font-variant {
- --tw-ordinal: ordinal;
- --tw-numeric-spacing: tabular-nums;
- font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
- var(--tw-numeric-spacing) var(--tw-numeric-fraction);
- }
- .custom-component {
- background: #123456;
- }
- *,
- :before,
- :after,
- ::backdrop {
- padding: 5px;
- }
- .foo .bg-black {
- appearance: none;
- }
- .inset-6 {
- inset: 1.5rem;
- }
- .inset-x-1 {
- left: 0.25rem;
- right: 0.25rem;
- }
- .end-8 {
- inset-inline-end: 2rem;
- }
- .start-4 {
- inset-inline-start: 1rem;
- }
- .mx-1 {
- margin-left: 0.25rem;
- margin-right: 0.25rem;
- }
- .me-8 {
- margin-inline-end: 2rem;
- }
- .ms-4 {
- margin-inline-start: 1rem;
- }
- .mt-6 {
- margin-top: 1.5rem;
- }
- .line-clamp-2 {
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- display: -webkit-box;
- overflow: hidden;
- }
- .line-clamp-\[33\] {
- -webkit-line-clamp: 33;
- -webkit-box-orient: vertical;
- display: -webkit-box;
- overflow: hidden;
- }
- .line-clamp-\[var\(--line-clamp-variable\)\] {
- -webkit-line-clamp: var(--line-clamp-variable);
- -webkit-box-orient: vertical;
- display: -webkit-box;
- overflow: hidden;
- }
- .line-clamp-none {
- -webkit-line-clamp: none;
- -webkit-box-orient: horizontal;
- display: block;
- overflow: visible;
- }
- .scale-50 {
- --tw-scale-x: 0.5;
- --tw-scale-y: 0.5;
- transform: translate(var(--tw-translate-x), 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));
- }
- .transform {
- transform: translate(var(--tw-translate-x), 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));
- }
- .grid-cols-\[200px\,repeat\(auto-fill\,minmax\(15\%\,100px\)\)\,300px\] {
- grid-template-columns: 200px repeat(auto-fill, minmax(15%, 100px)) 300px;
- }
- .rounded-e {
- border-start-end-radius: 0.25rem;
- border-end-end-radius: 0.25rem;
- }
- .rounded-s {
- border-start-start-radius: 0.25rem;
- border-end-start-radius: 0.25rem;
- }
- .rounded-es {
- border-end-start-radius: 0.25rem;
- }
- .rounded-ss {
- border-start-start-radius: 0.25rem;
- }
- .border-2 {
- border-width: 2px;
- }
- .border-e-4 {
- border-inline-end-width: 4px;
- }
- .border-s-0 {
- border-inline-start-width: 0;
- }
- .border-black {
- border-color: #000;
- }
- .border-e-red-400 {
- border-inline-end-color: #f87171;
- }
- .border-s-green-500 {
- border-inline-start-color: #22c55e;
- }
- .bg-black {
- background-color: #000;
- }
- .bg-green-500 {
- background-color: #22c55e;
- }
- .bg-gradient-to-r {
- background-image: linear-gradient(to right, var(--tw-gradient-stops));
- }
- .bg-hero--home-1 {
- background-image: url('/images/homepage-1.jpg');
- }
- .from-foo {
- --tw-gradient-from: #bada55 var(--tw-gradient-from-position);
- --tw-gradient-to: #bada5500 var(--tw-gradient-to-position);
- --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
- }
- .px-1 {
- padding-left: 0.25rem;
- padding-right: 0.25rem;
- }
- .pe-8 {
- padding-inline-end: 2rem;
- }
- .ps-4 {
- padding-inline-start: 1rem;
- }
- .pt-6 {
- padding-top: 1.5rem;
- }
- .text-center {
- text-align: center;
- }
- .font-medium {
- font-weight: 500;
- }
- .shadow-md {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
- 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
- .shadow-sm {
- --tw-shadow: 0 1px 2px 0 #0000000d;
- --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
- .magic-none {
- magic: none;
- }
- .magic-tons {
- magic: tons;
- }
- .custom-util {
- background: #abcdef;
- }
- *,
- :before,
- :after,
- ::backdrop {
- margin: 10px;
- }
- .hover\:container:hover {
- width: 100%;
- }
- @media (min-width: 640px) {
- .hover\:container:hover {
- max-width: 640px;
- }
- }
- @media (min-width: 768px) {
- .hover\:container:hover {
- max-width: 768px;
- }
- }
- @media (min-width: 1024px) {
- .hover\:container:hover {
- max-width: 1024px;
- }
- }
- @media (min-width: 1280px) {
- .hover\:container:hover {
- max-width: 1280px;
- }
- }
- @media (min-width: 1536px) {
- .hover\:container:hover {
- max-width: 1536px;
- }
- }
- @media (min-width: 640px) {
- .sm\:container {
- width: 100%;
- }
- @media (min-width: 640px) {
- .sm\:container {
- max-width: 640px;
- }
- }
- @media (min-width: 768px) {
- .sm\:container {
- max-width: 768px;
- }
- }
- @media (min-width: 1024px) {
- .sm\:container {
- max-width: 1024px;
- }
- }
- @media (min-width: 1280px) {
- .sm\:container {
- max-width: 1280px;
- }
- }
- @media (min-width: 1536px) {
- .sm\:container {
- max-width: 1536px;
- }
- }
- }
- @media (min-width: 768px) {
- .md\:container {
- width: 100%;
- }
- @media (min-width: 640px) {
- .md\:container {
- max-width: 640px;
- }
- }
- @media (min-width: 768px) {
- .md\:container {
- max-width: 768px;
- }
- }
- @media (min-width: 1024px) {
- .md\:container {
- max-width: 1024px;
- }
- }
- @media (min-width: 1280px) {
- .md\:container {
- max-width: 1280px;
- }
- }
- @media (min-width: 1536px) {
- .md\:container {
- max-width: 1536px;
- }
- }
- }
- .first\:pt-0:first-child {
- padding-top: 0;
- }
- .hover\:scale-75:hover {
- --tw-scale-x: 0.75;
- --tw-scale-y: 0.75;
- transform: translate(var(--tw-translate-x), 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\:font-bold:hover {
- font-weight: 700;
- }
- .hover\:shadow-lg:hover {
- --tw-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;
- --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color),
- 0 4px 6px -4px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
- .hover\:custom-util:hover {
- background: #abcdef;
- }
- .focus\:font-normal:focus {
- font-weight: 400;
- }
- .focus\:ring-2:focus {
- --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(2px + 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);
- }
- .focus\:ring-blue-500:focus {
- --tw-ring-color: #3b82f6;
- }
- .focus\:hover\:font-light:hover:focus {
- font-weight: 300;
- }
- .disabled\:font-bold:disabled {
- font-weight: 700;
- }
- .group:hover .group-hover\:opacity-100 {
- opacity: 1;
- }
- .group:hover .group-hover\:custom-util {
- background: #abcdef;
- }
- .group:active .group-active\:opacity-10 {
- opacity: 0.1;
- }
- .foo\:custom-util,
- .foo\:hover\:custom-util:hover {
- background: #abcdef !important;
- }
- @media (prefers-reduced-motion: no-preference) {
- .motion-safe\:transition {
- transition-property: color, background-color, border-color, text-decoration-color, fill,
- stroke, opacity, box-shadow, transform, filter, backdrop-filter;
- transition-duration: 0.15s;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- }
- .motion-safe\:custom-util {
- background: #abcdef;
- }
- }
- @media (prefers-reduced-motion: reduce) {
- .motion-reduce\:transition {
- transition-property: color, background-color, border-color, text-decoration-color, fill,
- stroke, opacity, box-shadow, transform, filter, backdrop-filter;
- transition-duration: 0.15s;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- }
- }
- :is(.dark .dark\:custom-util) {
- background: #abcdef;
- }
- @media (min-width: 640px) {
- .sm\:text-center {
- text-align: center;
- }
- .sm\:tabular-nums {
- --tw-numeric-spacing: tabular-nums;
- font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
- var(--tw-numeric-spacing) var(--tw-numeric-fraction);
- }
- .sm\:custom-util {
- background: #abcdef;
- }
- @media (prefers-reduced-motion: no-preference) {
- .group:active .sm\:motion-safe\:group-active\:focus\:opacity-10:focus {
- opacity: 0.1;
- }
- }
- }
- @media (min-width: 768px) {
- .md\:text-center {
- text-align: center;
- }
- .md\:opacity-50 {
- opacity: 0.5;
- }
- .md\:shadow-sm {
- --tw-shadow: 0 1px 2px 0 #0000000d;
- --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
- .md\:hover\:border-r-blue-500\/30:hover {
- border-right-color: #3b82f64d;
- }
- .md\:hover\:opacity-20:hover {
- opacity: 0.2;
- }
- @media (prefers-reduced-motion: no-preference) {
- .md\:motion-safe\:hover\:transition:hover {
- transition-property: color, background-color, border-color, text-decoration-color,
- fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
- transition-duration: 0.15s;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- }
- :is(.dark .md\:dark\:motion-safe\:foo\:active\:custom-util:active) {
- background: #abcdef !important;
- }
- }
- @media (min-width: 640px) {
- .md\:sm\:text-center {
- text-align: center;
- }
- }
- }
- @media (min-width: 1280px) and (max-width: 1535px) {
- .range\:text-right {
- text-align: right;
- }
- }
- @media (min-width: 640px) and (max-width: 767px), (max-width: 868px) {
- .multi\:text-left {
- text-align: left;
- }
- }
- `)
- })
+ }
+ `)
})
})
diff --git a/tests/layer-at-rules.test.js b/tests/layer-at-rules.test.js
index db98bbeb5..ac1b11bd5 100644
--- a/tests/layer-at-rules.test.js
+++ b/tests/layer-at-rules.test.js
@@ -1,321 +1,319 @@
-import { crosscheck, run, html, css, defaults } from './util/run'
+import { run, html, css, defaults } from './util/run'
-crosscheck(() => {
- test('custom user-land utilities', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: { preflight: false },
- theme: {},
- plugins: [],
+test('custom user-land utilities', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {},
+ plugins: [],
+ }
+
+ let input = css`
+ @layer utilities {
+ .align-banana {
+ text-align: banana;
+ }
}
- let input = css`
- @layer utilities {
- .align-banana {
- text-align: banana;
- }
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+
+ @layer utilities {
+ .align-chocolate {
+ text-align: chocolate;
}
+ }
+ `
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
-
- @layer utilities {
- .align-chocolate {
- text-align: chocolate;
- }
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults}
+ .uppercase {
+ text-transform: uppercase;
}
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- .uppercase {
- text-transform: uppercase;
- }
- .align-banana,
- .hover\:align-banana:hover {
- text-align: banana;
- }
- .focus\:hover\:align-chocolate:hover:focus {
- text-align: chocolate;
- }
- `)
- })
+ .align-banana,
+ .hover\:align-banana:hover {
+ text-align: banana;
+ }
+ .focus\:hover\:align-chocolate:hover:focus {
+ text-align: chocolate;
+ }
+ `)
})
+})
- test('comments can be used inside layers without crashing', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: { preflight: false },
- theme: {},
- plugins: [],
+test('comments can be used inside layers without crashing', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {},
+ plugins: [],
+ }
+
+ let input = css`
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+
+ @layer base {
+ /* Important base */
+ div {
+ background-color: #bada55;
+ }
}
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
-
- @layer base {
- /* Important base */
- div {
- background-color: #bada55;
- }
+ @layer utilities {
+ /* Important utility */
+ .important-utility {
+ text-align: banana;
}
+ }
- @layer utilities {
- /* Important utility */
+ @layer components {
+ /* Important component */
+ .important-component {
+ text-align: banana;
+ }
+ }
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ div {
+ background-color: #bada55;
+ }
+ ${defaults}
+ .important-component,
.important-utility {
- text-align: banana;
- }
+ text-align: banana;
}
-
- @layer components {
- /* Important component */
- .important-component {
- text-align: banana;
- }
- }
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- div {
- background-color: #bada55;
- }
- ${defaults}
- .important-component,
- .important-utility {
- text-align: banana;
- }
- `)
- })
+ `)
})
+})
- test('comments can be used inside layers (with important) without crashing', () => {
- let config = {
- important: true,
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: { preflight: false },
- theme: {},
- plugins: [],
+test('comments can be used inside layers (with important) without crashing', () => {
+ let config = {
+ important: true,
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {},
+ plugins: [],
+ }
+
+ let input = css`
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+
+ @layer base {
+ /* Important base */
+ div {
+ background-color: #bada55;
+ }
}
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
-
- @layer base {
- /* Important base */
- div {
- background-color: #bada55;
- }
+ @layer utilities {
+ /* Important utility */
+ .important-utility {
+ text-align: banana;
}
+ }
- @layer utilities {
- /* Important utility */
- .important-utility {
- text-align: banana;
- }
+ @layer components {
+ /* Important component */
+ .important-component {
+ text-align: banana;
}
+ }
+ `
- @layer components {
- /* Important component */
- .important-component {
- text-align: banana;
- }
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ div {
+ background-color: #bada55;
}
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- div {
- background-color: #bada55;
- }
- ${defaults}
- .important-component {
- text-align: banana;
- }
- .important-utility {
- text-align: banana !important;
- }
- `)
- })
+ ${defaults}
+ .important-component {
+ text-align: banana;
+ }
+ .important-utility {
+ text-align: banana !important;
+ }
+ `)
})
+})
- test('layers are grouped and inserted at the matching @tailwind rule', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addBase, addComponents, addUtilities }) {
- addBase({ body: { margin: 0 } })
+test('layers are grouped and inserted at the matching @tailwind rule', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function ({ addBase, addComponents, addUtilities }) {
+ addBase({ body: { margin: 0 } })
- addComponents({
- '.input': { background: 'white' },
- })
+ addComponents({
+ '.input': { background: 'white' },
+ })
- addUtilities({
- '.float-squirrel': { float: 'squirrel' },
- })
- },
- ],
- corePlugins: { preflight: false },
+ addUtilities({
+ '.float-squirrel': { float: 'squirrel' },
+ })
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @layer vanilla {
+ strong {
+ font-weight: medium;
+ }
}
- let input = css`
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+
+ @layer components {
+ .btn {
+ background: blue;
+ }
+ }
+
+ @layer utilities {
+ .align-banana {
+ text-align: banana;
+ }
+ }
+
+ @layer base {
+ h1 {
+ font-weight: bold;
+ }
+ }
+
+ @layer components {
+ .card {
+ border-radius: 12px;
+ }
+ }
+
+ @layer base {
+ p {
+ font-weight: normal;
+ }
+ }
+
+ @layer utilities {
+ .align-sandwich {
+ text-align: sandwich;
+ }
+ }
+
+ @layer chocolate {
+ a {
+ text-decoration: underline;
+ }
+ }
+ `
+
+ expect.assertions(2)
+
+ return run(input, config).then((result) => {
+ expect(result.warnings().length).toBe(0)
+ expect(result.css).toMatchFormattedCss(css`
@layer vanilla {
strong {
font-weight: medium;
}
}
-
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
-
- @layer components {
- .btn {
- background: blue;
- }
+ body {
+ margin: 0;
}
-
- @layer utilities {
- .align-banana {
- text-align: banana;
- }
+ h1 {
+ font-weight: bold;
}
-
- @layer base {
- h1 {
- font-weight: bold;
- }
+ p {
+ font-weight: normal;
}
-
- @layer components {
- .card {
- border-radius: 12px;
- }
+ ${defaults}
+ .input {
+ background: #fff;
}
-
- @layer base {
- p {
- font-weight: normal;
- }
+ .btn {
+ background: #00f;
}
-
- @layer utilities {
- .align-sandwich {
- text-align: sandwich;
- }
+ .card {
+ border-radius: 12px;
+ }
+ .float-squirrel {
+ float: squirrel;
+ }
+ .align-banana {
+ text-align: banana;
+ }
+ .align-sandwich {
+ text-align: sandwich;
}
-
@layer chocolate {
a {
text-decoration: underline;
}
}
- `
-
- expect.assertions(2)
-
- return run(input, config).then((result) => {
- expect(result.warnings().length).toBe(0)
- expect(result.css).toMatchFormattedCss(css`
- @layer vanilla {
- strong {
- font-weight: medium;
- }
- }
- body {
- margin: 0;
- }
- h1 {
- font-weight: bold;
- }
- p {
- font-weight: normal;
- }
- ${defaults}
- .input {
- background: #fff;
- }
- .btn {
- background: #00f;
- }
- .card {
- border-radius: 12px;
- }
- .float-squirrel {
- float: squirrel;
- }
- .align-banana {
- text-align: banana;
- }
- .align-sandwich {
- text-align: sandwich;
- }
- @layer chocolate {
- a {
- text-decoration: underline;
- }
- }
- `)
- })
- })
-
- it('should keep `@supports` rules inside `@layer`s', () => {
- let config = {
- content: [{ raw: html`
` }],
- plugins: [],
- }
-
- let input = css`
- @tailwind utilities;
-
- @layer utilities {
- .test {
- --tw-test: 1;
- }
-
- @supports (backdrop-filter: blur(1px)) {
- .test {
- --tw-test: 0.9;
- }
- }
- }
- `
-
- return run(input, config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .test {
- --tw-test: 1;
- }
- @supports (backdrop-filter: blur(1px)) {
- .test {
- --tw-test: 0.9;
- }
- }
- `)
- })
+ `)
+ })
+})
+
+it('should keep `@supports` rules inside `@layer`s', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ plugins: [],
+ }
+
+ let input = css`
+ @tailwind utilities;
+
+ @layer utilities {
+ .test {
+ --tw-test: 1;
+ }
+
+ @supports (backdrop-filter: blur(1px)) {
+ .test {
+ --tw-test: 0.9;
+ }
+ }
+ }
+ `
+
+ return run(input, config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .test {
+ --tw-test: 1;
+ }
+ @supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
+ .test {
+ --tw-test: 0.9;
+ }
+ }
+ `)
})
})
diff --git a/tests/layer-without-tailwind.test.js b/tests/layer-without-tailwind.test.js
index f15d43351..4122873c9 100644
--- a/tests/layer-without-tailwind.test.js
+++ b/tests/layer-without-tailwind.test.js
@@ -1,81 +1,79 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(() => {
- test('using @layer without @tailwind', async () => {
- let config = {
- content: [{ raw: html`
` }],
- }
+test('using @layer without @tailwind', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ }
- let input = css`
- @layer components {
- .foo {
- color: black;
- }
+ let input = css`
+ @layer components {
+ .foo {
+ color: black;
}
- `
-
- await expect(run(input, config)).rejects.toThrowError(
- '`@layer components` is used but no matching `@tailwind components` directive is present.'
- )
- })
-
- test('using @responsive without @tailwind', async () => {
- let config = {
- content: [{ raw: html`
` }],
}
+ `
- let input = css`
- @responsive {
- .foo {
- color: black;
- }
+ await expect(run(input, config)).rejects.toThrowError(
+ '`@layer components` is used but no matching `@tailwind components` directive is present.'
+ )
+})
+
+test('using @responsive without @tailwind', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ }
+
+ let input = css`
+ @responsive {
+ .foo {
+ color: black;
}
- `
-
- await expect(run(input, config)).rejects.toThrowError(
- '`@responsive` is used but `@tailwind utilities` is missing.'
- )
- })
-
- test('using @variants without @tailwind', async () => {
- let config = {
- content: [{ raw: html`
` }],
}
+ `
- let input = css`
- @variants hover {
- .foo {
- color: black;
- }
+ await expect(run(input, config)).rejects.toThrowError(
+ '`@responsive` is used but `@tailwind utilities` is missing.'
+ )
+})
+
+test('using @variants without @tailwind', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ }
+
+ let input = css`
+ @variants hover {
+ .foo {
+ color: black;
}
- `
-
- await expect(run(input, config)).rejects.toThrowError(
- '`@variants` is used but `@tailwind utilities` is missing.'
- )
- })
-
- test('non-Tailwind @layer rules are okay', async () => {
- let config = {
- content: [{ raw: html`
` }],
}
+ `
- let input = css`
+ await expect(run(input, config)).rejects.toThrowError(
+ '`@variants` is used but `@tailwind utilities` is missing.'
+ )
+})
+
+test('non-Tailwind @layer rules are okay', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ }
+
+ let input = css`
+ @layer custom {
+ .foo {
+ color: black;
+ }
+ }
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
@layer custom {
.foo {
- color: black;
+ color: #000;
}
}
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @layer custom {
- .foo {
- color: #000;
- }
- }
- `)
- })
+ `)
})
})
diff --git a/tests/match-components.test.js b/tests/match-components.test.js
index a8b546127..8f6241724 100644
--- a/tests/match-components.test.js
+++ b/tests/match-components.test.js
@@ -1,86 +1,82 @@
-import { crosscheck, run, html, css, defaults } from './util/run'
+import { run, html, css, defaults } from './util/run'
-crosscheck(() => {
- it('should be possible to matchComponents', () => {
- let config = {
- content: [
- {
- raw: html``,
- },
- ],
- corePlugins: {
- preflight: false,
+it('should be possible to matchComponents', () => {
+ let config = {
+ content: [
+ {
+ raw: html``,
},
- plugins: [
- function ({ matchComponents }) {
- matchComponents({
- card: (value) => {
- return [
- { color: value },
- {
- '.card-header': {
- borderTopWidth: 3,
- borderTopColor: value,
- },
+ ],
+ corePlugins: {
+ preflight: false,
+ },
+ plugins: [
+ function ({ matchComponents }) {
+ matchComponents({
+ card: (value) => {
+ return [
+ { color: value },
+ {
+ '.card-header': {
+ borderTopWidth: 3,
+ borderTopColor: value,
},
- {
- '.card-footer': {
- borderBottomWidth: 3,
- borderBottomColor: value,
- },
+ },
+ {
+ '.card-footer': {
+ borderBottomWidth: 3,
+ borderBottomColor: value,
},
- ]
- },
- })
- },
- ],
- }
+ },
+ ]
+ },
+ })
+ },
+ ],
+ }
- return run('@tailwind base; @tailwind components; @tailwind utilities', config).then(
- (result) => {
- return expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- .card-\[\#0088cc\] {
- color: #08c;
- }
- .card-\[\#0088cc\] .card-header {
- border-top-width: 3px;
- border-top-color: #08c;
- }
- .card-\[\#0088cc\] .card-footer {
- border-bottom-width: 3px;
- border-bottom-color: #08c;
- }
- .text-center {
- text-align: center;
- }
- .font-bold {
- font-weight: 700;
- }
- .shadow {
- --tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
- --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
- 0 1px 2px -1px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
- .hover\:card-\[\#f0f\]:hover {
- color: #f0f;
- }
- .hover\:card-\[\#f0f\]:hover .card-header {
- border-top-width: 3px;
- border-top-color: #f0f;
- }
- .hover\:card-\[\#f0f\]:hover .card-footer {
- border-bottom-width: 3px;
- border-bottom-color: #f0f;
- }
- `)
+ return run('@tailwind base; @tailwind components; @tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ ${defaults}
+ .card-\[\#0088cc\] {
+ color: #08c;
}
- )
+ .card-\[\#0088cc\] .card-header {
+ border-top-width: 3px;
+ border-top-color: #08c;
+ }
+ .card-\[\#0088cc\] .card-footer {
+ border-bottom-width: 3px;
+ border-bottom-color: #08c;
+ }
+ .text-center {
+ text-align: center;
+ }
+ .font-bold {
+ font-weight: 700;
+ }
+ .shadow {
+ --tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
+ --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
+ 0 1px 2px -1px var(--tw-shadow-color);
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
+ var(--tw-shadow);
+ }
+ .hover\:card-\[\#f0f\]:hover {
+ color: #f0f;
+ }
+ .hover\:card-\[\#f0f\]:hover .card-header {
+ border-top-width: 3px;
+ border-top-color: #f0f;
+ }
+ .hover\:card-\[\#f0f\]:hover .card-footer {
+ border-bottom-width: 3px;
+ border-bottom-color: #f0f;
+ }
+ `)
})
})
diff --git a/tests/match-utilities.test.js b/tests/match-utilities.test.js
index 9b3cd4c56..a9705b021 100644
--- a/tests/match-utilities.test.js
+++ b/tests/match-utilities.test.js
@@ -1,575 +1,573 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(() => {
- test('match utilities with modifiers', async () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: { preflight: false },
+test('match utilities with modifiers', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
- plugins: [
- ({ matchUtilities }) => {
- matchUtilities(
- {
- test: (value, { modifier }) => ({
- color: `${value}_${modifier}`,
- }),
+ plugins: [
+ ({ matchUtilities }) => {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({
+ color: `${value}_${modifier}`,
+ }),
+ },
+ {
+ values: {
+ DEFAULT: 'default',
+ bar: 'bar',
+ '1': 'one',
+ '2': 'two',
+ '1/foo': 'onefoo',
+ '[8]/[9]': 'eightnine',
},
- {
- values: {
- DEFAULT: 'default',
- bar: 'bar',
- '1': 'one',
- '2': 'two',
- '1/foo': 'onefoo',
- '[8]/[9]': 'eightnine',
- },
- modifiers: 'any',
- }
- )
- },
- ],
+ modifiers: 'any',
+ }
+ )
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .test {
+ color: default_null;
}
+ .test-1\/\[foo\] {
+ color: one_[foo];
+ }
+ .test-1\/foo {
+ color: onefoo_null;
+ }
+ .test-2\/foo {
+ color: two_foo;
+ }
+ .test-\[8\]\/\[9\] {
+ color: eightnine_null;
+ }
+ .test\/\[foo\] {
+ color: default_[foo];
+ }
+ .test\/foo {
+ color: default_foo;
+ }
+ `)
+})
- let input = css`
- @tailwind utilities;
- `
+test('match utilities with modifiers in the config', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
- let result = await run(input, config)
+ plugins: [
+ ({ matchUtilities }) => {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({
+ color: `${value}_${modifier}`,
+ }),
+ },
+ {
+ values: {
+ DEFAULT: 'default',
+ bar: 'bar',
+ '1': 'one',
+ },
+ modifiers: {
+ foo: 'mewtwo',
+ },
+ }
+ )
+ },
+ ],
+ }
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .test {
+ color: default_null;
+ }
+ .test-1\/\[bar\] {
+ color: one_bar;
+ }
+ .test-1\/foo {
+ color: one_mewtwo;
+ }
+ .test\/\[bar\] {
+ color: default_bar;
+ }
+ .test\/foo {
+ color: default_mewtwo;
+ }
+ `)
+})
+
+test('match utilities can omit utilities by returning null', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+
+ plugins: [
+ ({ matchUtilities }) => {
+ matchUtilities(
+ {
+ test: (value, { modifier }) =>
+ modifier === 'bad'
+ ? null
+ : {
+ color: `${value}_${modifier}`,
+ },
+ },
+ {
+ values: {
+ DEFAULT: 'default',
+ bar: 'bar',
+ '1': 'one',
+ },
+ modifiers: 'any',
+ }
+ )
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .test {
+ color: default_null;
+ }
+ .test\/good {
+ color: default_good;
+ }
+ `)
+})
+
+test('matching utilities with a basic configured value', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value) => ({ value }),
+ },
+ {
+ values: {
+ foo: 'value_foo',
+ },
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
- .test {
- color: default_null;
- }
- .test-1\/\[foo\] {
- color: one_[foo];
- }
- .test-1\/foo {
- color: onefoo_null;
- }
- .test-2\/foo {
- color: two_foo;
- }
- .test-\[8\]\/\[9\] {
- color: eightnine_null;
- }
- .test\/\[foo\] {
- color: default_[foo];
- }
- .test\/foo {
- color: default_foo;
+ .test-foo {
+ value: value_foo;
+ }
+ `)
+ })
+})
+
+test('matching utilities with an arbitrary value and configured modifier', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
+ },
+ {
+ modifiers: {
+ bar: 'configured_bar',
+ },
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .test-\[foo\]\/bar {
+ value: foo;
+ modifier: configured_bar;
+ }
+ `)
+ })
+})
+
+test('matching utilities with an configured value and an arbitrary modifier (raw)', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
+ },
+ {
+ values: {
+ foo: 'configured_foo',
+ },
+ modifiers: 'any', // Raw `[value]`
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .test-foo\/\[bar\] {
+ value: configured_foo;
+ modifier: [bar];
+ }
+ `)
+ })
+})
+
+test('matching utilities with an configured value and an arbitrary modifier (non-raw)', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
+ },
+ {
+ values: {
+ foo: 'configured_foo',
+ },
+ modifiers: {},
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .test-foo\/\[bar\] {
+ value: configured_foo;
+ modifier: bar;
+ }
+ `)
+ })
+})
+
+test('matching utilities with an configured value and a configured modifier', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
+ },
+ {
+ values: {
+ foo: 'configured_foo',
+ },
+ modifiers: {
+ bar: 'configured_bar',
+ },
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .test-foo\/bar {
+ value: configured_foo;
+ modifier: configured_bar;
+ }
+ `)
+ })
+})
+
+test('matching utilities with an arbitrary value and an arbitrary modifier (raw)', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
+ },
+ {
+ modifiers: 'any',
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .test-\[foo\]\/\[bar\] {
+ value: foo;
+ modifier: [bar];
+ }
+ `)
+ })
+})
+
+test('matching utilities with an arbitrary value and an arbitrary modifier (non-raw)', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
+ },
+ {
+ modifiers: {},
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .test-\[foo\]\/\[bar\] {
+ value: foo;
+ modifier: bar;
+ }
+ `)
+ })
+})
+
+test('matching utilities with a lookup value that looks like an arbitrary value and modifier', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
+ },
+ {
+ values: {
+ '[foo]/[bar]': 'hello',
+ },
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .test-\[foo\]\/\[bar\] {
+ value: hello;
+ }
+ `)
+ })
+})
+
+test('matching utilities with a lookup value that looks like an arbitrary value and modifier (with modifiers = any)', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
+ },
+ {
+ values: {
+ '[foo]/[bar]': 'hello',
+ },
+ modifiers: 'any',
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .test-\[foo\]\/\[bar\] {
+ value: hello;
+ }
+ `)
+ })
+})
+
+test('matching utilities with a lookup value that looks like an arbitrary value and modifier (with modifiers = {})', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
+ },
+ {
+ values: {
+ '[foo]/[bar]': 'hello',
+ },
+ modifiers: {},
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .test-\[foo\]\/\[bar\] {
+ value: hello;
+ }
+ `)
+ })
+})
+
+test('matching utilities with a lookup value that looks like an arbitrary value and a configured modifier', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
+ },
+ {
+ values: {
+ '[foo]/bar': 'hello',
+ },
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .test-\[foo\]\/bar {
+ value: hello;
+ }
+ `)
+ })
+})
+
+test('matching utilities with a lookup value that looks like a configured value and an arbitrary modifier', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
+ },
+ {
+ values: {
+ 'foo/[bar]': 'hello',
+ },
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .test-foo\/\[bar\] {
+ value: hello;
+ }
+ `)
+ })
+})
+
+test('matching utilities with a lookup value that does not match the configured type', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {},
+ plugins: [
+ function ({ matchUtilities }) {
+ matchUtilities(
+ {
+ test: (value, { modifier }) => ({ value, modifier }),
+ },
+ {
+ values: {
+ foo: 'not-a-percentage',
+ },
+ type: ['percentage'],
+ }
+ )
+ },
+ ],
+ corePlugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .test-foo {
+ value: not-a-percentage;
}
`)
})
-
- test('match utilities with modifiers in the config', async () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: { preflight: false },
-
- plugins: [
- ({ matchUtilities }) => {
- matchUtilities(
- {
- test: (value, { modifier }) => ({
- color: `${value}_${modifier}`,
- }),
- },
- {
- values: {
- DEFAULT: 'default',
- bar: 'bar',
- '1': 'one',
- },
- modifiers: {
- foo: 'mewtwo',
- },
- }
- )
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .test {
- color: default_null;
- }
- .test-1\/\[bar\] {
- color: one_bar;
- }
- .test-1\/foo {
- color: one_mewtwo;
- }
- .test\/\[bar\] {
- color: default_bar;
- }
- .test\/foo {
- color: default_mewtwo;
- }
- `)
- })
-
- test('match utilities can omit utilities by returning null', async () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: { preflight: false },
-
- plugins: [
- ({ matchUtilities }) => {
- matchUtilities(
- {
- test: (value, { modifier }) =>
- modifier === 'bad'
- ? null
- : {
- color: `${value}_${modifier}`,
- },
- },
- {
- values: {
- DEFAULT: 'default',
- bar: 'bar',
- '1': 'one',
- },
- modifiers: 'any',
- }
- )
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .test {
- color: default_null;
- }
- .test\/good {
- color: default_good;
- }
- `)
- })
-
- test('matching utilities with a basic configured value', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value) => ({ value }),
- },
- {
- values: {
- foo: 'value_foo',
- },
- }
- )
- },
- ],
- corePlugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-foo {
- value: value_foo;
- }
- `)
- })
- })
-
- test('matching utilities with an arbitrary value and configured modifier', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- modifiers: {
- bar: 'configured_bar',
- },
- }
- )
- },
- ],
- corePlugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-\[foo\]\/bar {
- value: foo;
- modifier: configured_bar;
- }
- `)
- })
- })
-
- test('matching utilities with an configured value and an arbitrary modifier (raw)', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- values: {
- foo: 'configured_foo',
- },
- modifiers: 'any', // Raw `[value]`
- }
- )
- },
- ],
- corePlugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-foo\/\[bar\] {
- value: configured_foo;
- modifier: [bar];
- }
- `)
- })
- })
-
- test('matching utilities with an configured value and an arbitrary modifier (non-raw)', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- values: {
- foo: 'configured_foo',
- },
- modifiers: {},
- }
- )
- },
- ],
- corePlugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-foo\/\[bar\] {
- value: configured_foo;
- modifier: bar;
- }
- `)
- })
- })
-
- test('matching utilities with an configured value and a configured modifier', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- values: {
- foo: 'configured_foo',
- },
- modifiers: {
- bar: 'configured_bar',
- },
- }
- )
- },
- ],
- corePlugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-foo\/bar {
- value: configured_foo;
- modifier: configured_bar;
- }
- `)
- })
- })
-
- test('matching utilities with an arbitrary value and an arbitrary modifier (raw)', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- modifiers: 'any',
- }
- )
- },
- ],
- corePlugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-\[foo\]\/\[bar\] {
- value: foo;
- modifier: [bar];
- }
- `)
- })
- })
-
- test('matching utilities with an arbitrary value and an arbitrary modifier (non-raw)', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- modifiers: {},
- }
- )
- },
- ],
- corePlugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-\[foo\]\/\[bar\] {
- value: foo;
- modifier: bar;
- }
- `)
- })
- })
-
- test('matching utilities with a lookup value that looks like an arbitrary value and modifier', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- values: {
- '[foo]/[bar]': 'hello',
- },
- }
- )
- },
- ],
- corePlugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-\[foo\]\/\[bar\] {
- value: hello;
- }
- `)
- })
- })
-
- test('matching utilities with a lookup value that looks like an arbitrary value and modifier (with modifiers = any)', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- values: {
- '[foo]/[bar]': 'hello',
- },
- modifiers: 'any',
- }
- )
- },
- ],
- corePlugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-\[foo\]\/\[bar\] {
- value: hello;
- }
- `)
- })
- })
-
- test('matching utilities with a lookup value that looks like an arbitrary value and modifier (with modifiers = {})', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- values: {
- '[foo]/[bar]': 'hello',
- },
- modifiers: {},
- }
- )
- },
- ],
- corePlugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-\[foo\]\/\[bar\] {
- value: hello;
- }
- `)
- })
- })
-
- test('matching utilities with a lookup value that looks like an arbitrary value and a configured modifier', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- values: {
- '[foo]/bar': 'hello',
- },
- }
- )
- },
- ],
- corePlugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-\[foo\]\/bar {
- value: hello;
- }
- `)
- })
- })
-
- test('matching utilities with a lookup value that looks like a configured value and an arbitrary modifier', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- values: {
- 'foo/[bar]': 'hello',
- },
- }
- )
- },
- ],
- corePlugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-foo\/\[bar\] {
- value: hello;
- }
- `)
- })
- })
-
- test('matching utilities with a lookup value that does not match the configured type', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {},
- plugins: [
- function ({ matchUtilities }) {
- matchUtilities(
- {
- test: (value, { modifier }) => ({ value, modifier }),
- },
- {
- values: {
- foo: 'not-a-percentage',
- },
- type: ['percentage'],
- }
- )
- },
- ],
- corePlugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .test-foo {
- value: not-a-percentage;
- }
- `)
- })
- })
})
diff --git a/tests/match-variants.test.js b/tests/match-variants.test.js
index e4330f7a9..2923b10c0 100644
--- a/tests/match-variants.test.js
+++ b/tests/match-variants.test.js
@@ -1,856 +1,820 @@
import resolveConfig from '../src/public/resolve-config'
import { createContext } from '../src/lib/setupContextUtils'
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(({ stable, oxide }) => {
- test('partial arbitrary variants', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('potato', (flavor) => `.potato-${flavor} &`)
- },
- ],
- }
+test('partial arbitrary variants', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('potato', (flavor) => `.potato-${flavor} &`)
+ },
+ ],
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .potato-baked .potato-\[baked\]\:w-3 {
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .potato-baked .potato-\[baked\]\:w-3 {
+ width: 0.75rem;
+ }
+ .potato-yellow .potato-\[yellow\]\:bg-yellow-200 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 240 138 / var(--tw-bg-opacity));
+ }
+ `)
+ })
+})
+
+test('partial arbitrary variants with at-rules', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('potato', (flavor) => `@media (potato: ${flavor})`)
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (potato: baked) {
+ .potato-\[baked\]\:w-3 {
width: 0.75rem;
}
- .potato-yellow .potato-\[yellow\]\:bg-yellow-200 {
+ }
+ @media (potato: yellow) {
+ .potato-\[yellow\]\:bg-yellow-200 {
--tw-bg-opacity: 1;
background-color: rgb(254 240 138 / var(--tw-bg-opacity));
}
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .potato-baked .potato-\[baked\]\:w-3 {
+ }
+ `)
+ })
+})
+
+test('partial arbitrary variants with at-rules and placeholder', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('potato', (flavor) => `@media (potato: ${flavor}) { &:potato }`)
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (potato: baked) {
+ .potato-\[baked\]\:w-3:potato {
width: 0.75rem;
}
- .potato-yellow .potato-\[yellow\]\:bg-yellow-200 {
- background-color: #fef08a;
+ }
+ @media (potato: yellow) {
+ .potato-\[yellow\]\:bg-yellow-200:potato {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 240 138 / var(--tw-bg-opacity));
}
- `)
- })
+ }
+ `)
})
+})
- test('partial arbitrary variants with at-rules', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('potato', (flavor) => `@media (potato: ${flavor})`)
- },
- ],
- }
+test('partial arbitrary variants with default values', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('tooltip', (side) => `&${side}`, {
+ values: {
+ bottom: '[data-location="bottom"]',
+ top: '[data-location="top"]',
+ },
+ })
+ },
+ ],
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- @media (potato: baked) {
- .potato-\[baked\]\:w-3 {
- width: 0.75rem;
- }
- }
- @media (potato: yellow) {
- .potato-\[yellow\]\:bg-yellow-200 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 240 138 / var(--tw-bg-opacity));
- }
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- @media (potato: baked) {
- .potato-\[baked\]\:w-3 {
- width: 0.75rem;
- }
- }
- @media (potato: yellow) {
- .potato-\[yellow\]\:bg-yellow-200 {
- background-color: #fef08a;
- }
- }
- `)
- })
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .tooltip-bottom\:mt-2[data-location='bottom'] {
+ margin-top: 0.5rem;
+ }
+ .tooltip-top\:mb-2[data-location='top'] {
+ margin-bottom: 0.5rem;
+ }
+ `)
})
+})
- test('partial arbitrary variants with at-rules and placeholder', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('potato', (flavor) => `@media (potato: ${flavor}) { &:potato }`)
- },
- ],
- }
+test('matched variant values maintain the sort order they are registered in', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('alphabet', (side) => `&${side}`, {
+ values: {
+ a: '[data-value="a"]',
+ b: '[data-value="b"]',
+ c: '[data-value="c"]',
+ d: '[data-value="d"]',
+ },
+ })
+ },
+ ],
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- @media (potato: baked) {
- .potato-\[baked\]\:w-3:potato {
- width: 0.75rem;
- }
- }
- @media (potato: yellow) {
- .potato-\[yellow\]\:bg-yellow-200:potato {
- --tw-bg-opacity: 1;
- background-color: rgb(254 240 138 / var(--tw-bg-opacity));
- }
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- @media (potato: baked) {
- .potato-\[baked\]\:w-3:potato {
- width: 0.75rem;
- }
- }
- @media (potato: yellow) {
- .potato-\[yellow\]\:bg-yellow-200:potato {
- background-color: #fef08a;
- }
- }
- `)
- })
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .alphabet-a\:underline[data-value='a'],
+ .alphabet-b\:underline[data-value='b'],
+ .alphabet-c\:underline[data-value='c'],
+ .alphabet-d\:underline[data-value='d'] {
+ text-decoration-line: underline;
+ }
+ `)
})
+})
- test('partial arbitrary variants with default values', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('tooltip', (side) => `&${side}`, {
- values: {
- bottom: '[data-location="bottom"]',
- top: '[data-location="top"]',
- },
- })
- },
- ],
- }
+test('matchVariant can return an array of format strings from the function', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('test', (selector) =>
+ selector.split(',').map((selector) => `&.${selector} > *`)
+ )
+ },
+ ],
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .tooltip-bottom\:mt-2[data-location='bottom'] {
- margin-top: 0.5rem;
- }
- .tooltip-top\:mb-2[data-location='top'] {
- margin-bottom: 0.5rem;
- }
- `)
- })
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .test-\[a\,b\,c\]\:underline.a > *,
+ .test-\[a\,b\,c\]\:underline.b > *,
+ .test-\[a\,b\,c\]\:underline.c > * {
+ text-decoration-line: underline;
+ }
+ `)
})
+})
- test('matched variant values maintain the sort order they are registered in', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('alphabet', (side) => `&${side}`, {
- values: {
- a: '[data-value="a"]',
- b: '[data-value="b"]',
- c: '[data-value="c"]',
- d: '[data-value="d"]',
- },
- })
- },
- ],
- }
+it('should be possible to sort variants', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(a.value) - parseInt(z.value)
+ },
+ })
+ },
+ ],
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .alphabet-a\:underline[data-value='a'],
- .alphabet-b\:underline[data-value='b'],
- .alphabet-c\:underline[data-value='c'],
- .alphabet-d\:underline[data-value='d'] {
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 500px) {
+ .testmin-\[500px\]\:underline {
text-decoration-line: underline;
}
- `)
- })
- })
-
- test('matchVariant can return an array of format strings from the function', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('test', (selector) =>
- selector.split(',').map((selector) => `&.${selector} > *`)
- )
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .test-\[a\,b\,c\]\:underline.a > *,
- .test-\[a\,b\,c\]\:underline.b > *,
- .test-\[a\,b\,c\]\:underline.c > * {
- text-decoration-line: underline;
+ }
+ @media (min-width: 700px) {
+ .testmin-\[700px\]\:italic {
+ font-style: italic;
}
- `)
- })
+ }
+ `)
})
+})
- it('should be possible to sort variants', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
- sort(a, z) {
- return parseInt(a.value) - parseInt(z.value)
- },
- })
- },
- ],
- }
+it('should be possible to compare arbitrary variants and hardcoded variants', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
+ values: {
+ example: '600px',
+ },
+ sort(a, z) {
+ return parseInt(a.value) - parseInt(z.value)
+ },
+ })
+ },
+ ],
+ }
- let input = css`
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 500px) {
- .testmin-\[500px\]\:underline {
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 500px) {
+ .testmin-\[500px\]\:italic {
+ font-style: italic;
+ }
+ }
+ @media (min-width: 600px) {
+ .testmin-example\:italic {
+ font-style: italic;
+ }
+ }
+ @media (min-width: 700px) {
+ .testmin-\[700px\]\:italic {
+ font-style: italic;
+ }
+ }
+ `)
+ })
+})
+
+it('should be possible to sort stacked arbitrary variants correctly', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(a.value) - parseInt(z.value)
+ },
+ })
+
+ matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(z.value) - parseInt(a.value)
+ },
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 100px) {
+ @media (max-width: 400px) {
+ .testmin-\[100px\]\:testmax-\[400px\]\:underline {
text-decoration-line: underline;
}
}
- @media (min-width: 700px) {
- .testmin-\[700px\]\:italic {
- font-style: italic;
+ @media (max-width: 350px) {
+ .testmin-\[100px\]\:testmax-\[350px\]\:underline {
+ text-decoration-line: underline;
}
}
- `)
- })
+ @media (max-width: 300px) {
+ .testmin-\[100px\]\:testmax-\[300px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ }
+ @media (min-width: 150px) {
+ @media (max-width: 400px) {
+ .testmin-\[150px\]\:testmax-\[400px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ }
+ `)
})
+})
- it('should be possible to compare arbitrary variants and hardcoded variants', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
- values: {
- example: '600px',
- },
- sort(a, z) {
- return parseInt(a.value) - parseInt(z.value)
- },
- })
- },
- ],
- }
+it('should maintain sort from other variants, if sort functions of arbitrary variants return 0', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(a.value) - parseInt(z.value)
+ },
+ })
- let input = css`
- @tailwind utilities;
- `
+ matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(z.value) - parseInt(a.value)
+ },
+ })
+ },
+ ],
+ }
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 500px) {
- .testmin-\[500px\]\:italic {
- font-style: italic;
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 100px) {
+ @media (max-width: 200px) {
+ .testmin-\[100px\]\:testmax-\[200px\]\:hover\:underline:hover,
+ .testmin-\[100px\]\:testmax-\[200px\]\:focus\:underline:focus {
+ text-decoration-line: underline;
}
}
- @media (min-width: 600px) {
- .testmin-example\:italic {
- font-style: italic;
- }
- }
- @media (min-width: 700px) {
- .testmin-\[700px\]\:italic {
- font-style: italic;
- }
- }
- `)
- })
+ }
+ `)
})
+})
- it('should be possible to sort stacked arbitrary variants correctly', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
- sort(a, z) {
- return parseInt(a.value) - parseInt(z.value)
- },
- })
+it('should sort arbitrary variants left to right (1)', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(a.value) - parseInt(z.value)
+ },
+ })
+ matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(z.value) - parseInt(a.value)
+ },
+ })
+ },
+ ],
+ }
- matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
- sort(a, z) {
- return parseInt(z.value) - parseInt(a.value)
- },
- })
- },
- ],
- }
+ let input = css`
+ @tailwind utilities;
+ `
- let input = css`
- @tailwind utilities;
- `
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 100px) {
+ @media (max-width: 400px) {
+ .testmin-\[100px\]\:testmax-\[400px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ @media (max-width: 300px) {
+ .testmin-\[100px\]\:testmax-\[300px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ }
+ @media (min-width: 200px) {
+ @media (max-width: 400px) {
+ .testmin-\[200px\]\:testmax-\[400px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ @media (max-width: 300px) {
+ .testmin-\[200px\]\:testmax-\[300px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ }
+ `)
+ })
+})
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
+it('should sort arbitrary variants left to right (2)', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(a.value) - parseInt(z.value)
+ },
+ })
+ matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
+ sort(a, z) {
+ return parseInt(z.value) - parseInt(a.value)
+ },
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (max-width: 400px) {
@media (min-width: 100px) {
- @media (max-width: 400px) {
- .testmin-\[100px\]\:testmax-\[400px\]\:underline {
- text-decoration-line: underline;
- }
- }
- @media (max-width: 350px) {
- .testmin-\[100px\]\:testmax-\[350px\]\:underline {
- text-decoration-line: underline;
- }
- }
- @media (max-width: 300px) {
- .testmin-\[100px\]\:testmax-\[300px\]\:underline {
- text-decoration-line: underline;
- }
- }
- }
- @media (min-width: 150px) {
- @media (max-width: 400px) {
- .testmin-\[150px\]\:testmax-\[400px\]\:underline {
- text-decoration-line: underline;
- }
- }
- }
- `)
- })
- })
-
- it('should maintain sort from other variants, if sort functions of arbitrary variants return 0', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
- sort(a, z) {
- return parseInt(a.value) - parseInt(z.value)
- },
- })
-
- matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
- sort(a, z) {
- return parseInt(z.value) - parseInt(a.value)
- },
- })
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 100px) {
- @media (max-width: 200px) {
- .testmin-\[100px\]\:testmax-\[200px\]\:hover\:underline:hover,
- .testmin-\[100px\]\:testmax-\[200px\]\:focus\:underline:focus {
- text-decoration-line: underline;
- }
- }
- }
- `)
- })
- })
-
- it('should sort arbitrary variants left to right (1)', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
- sort(a, z) {
- return parseInt(a.value) - parseInt(z.value)
- },
- })
- matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
- sort(a, z) {
- return parseInt(z.value) - parseInt(a.value)
- },
- })
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 100px) {
- @media (max-width: 400px) {
- .testmin-\[100px\]\:testmax-\[400px\]\:underline {
- text-decoration-line: underline;
- }
- }
- @media (max-width: 300px) {
- .testmin-\[100px\]\:testmax-\[300px\]\:underline {
- text-decoration-line: underline;
- }
+ .testmax-\[400px\]\:testmin-\[100px\]\:underline {
+ text-decoration-line: underline;
}
}
@media (min-width: 200px) {
- @media (max-width: 400px) {
- .testmin-\[200px\]\:testmax-\[400px\]\:underline {
- text-decoration-line: underline;
- }
- }
- @media (max-width: 300px) {
- .testmin-\[200px\]\:testmax-\[300px\]\:underline {
- text-decoration-line: underline;
- }
+ .testmax-\[400px\]\:testmin-\[200px\]\:underline {
+ text-decoration-line: underline;
}
}
- `)
- })
- })
+ }
- it('should sort arbitrary variants left to right (2)', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
- sort(a, z) {
- return parseInt(a.value) - parseInt(z.value)
- },
- })
- matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
- sort(a, z) {
- return parseInt(z.value) - parseInt(a.value)
- },
- })
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (max-width: 400px) {
- @media (min-width: 100px) {
- .testmax-\[400px\]\:testmin-\[100px\]\:underline {
- text-decoration-line: underline;
- }
+ @media (max-width: 300px) {
+ @media (min-width: 100px) {
+ .testmax-\[300px\]\:testmin-\[100px\]\:underline {
+ text-decoration-line: underline;
}
- @media (min-width: 200px) {
- .testmax-\[400px\]\:testmin-\[200px\]\:underline {
- text-decoration-line: underline;
+ }
+ @media (min-width: 200px) {
+ .testmax-\[300px\]\:testmin-\[200px\]\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ }
+ `)
+ })
+})
+
+it('should guarantee that we are not passing values from other variants to the wrong function', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
+ sort(a, z) {
+ let lookup = ['100px', '200px']
+ if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) {
+ throw new Error('We are seeing values that should not be there!')
}
+ return lookup.indexOf(a.value) - lookup.indexOf(z.value)
+ },
+ })
+ matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
+ sort(a, z) {
+ let lookup = ['300px', '400px']
+ if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) {
+ throw new Error('We are seeing values that should not be there!')
+ }
+ return lookup.indexOf(z.value) - lookup.indexOf(a.value)
+ },
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 100px) {
+ @media (max-width: 400px) {
+ .testmin-\[100px\]\:testmax-\[400px\]\:underline {
+ text-decoration-line: underline;
}
}
@media (max-width: 300px) {
- @media (min-width: 100px) {
- .testmax-\[300px\]\:testmin-\[100px\]\:underline {
- text-decoration-line: underline;
- }
- }
- @media (min-width: 200px) {
- .testmax-\[300px\]\:testmin-\[200px\]\:underline {
- text-decoration-line: underline;
- }
+ .testmin-\[100px\]\:testmax-\[300px\]\:underline {
+ text-decoration-line: underline;
}
}
- `)
- })
- })
+ }
- it('should guarantee that we are not passing values from other variants to the wrong function', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('testmin', (value) => `@media (min-width: ${value})`, {
- sort(a, z) {
- let lookup = ['100px', '200px']
- if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) {
- throw new Error('We are seeing values that should not be there!')
- }
- return lookup.indexOf(a.value) - lookup.indexOf(z.value)
- },
- })
- matchVariant('testmax', (value) => `@media (max-width: ${value})`, {
- sort(a, z) {
- let lookup = ['300px', '400px']
- if (lookup.indexOf(a.value) === -1 || lookup.indexOf(z.value) === -1) {
- throw new Error('We are seeing values that should not be there!')
- }
- return lookup.indexOf(z.value) - lookup.indexOf(a.value)
- },
- })
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 100px) {
- @media (max-width: 400px) {
- .testmin-\[100px\]\:testmax-\[400px\]\:underline {
- text-decoration-line: underline;
- }
- }
-
- @media (max-width: 300px) {
- .testmin-\[100px\]\:testmax-\[300px\]\:underline {
- text-decoration-line: underline;
- }
+ @media (min-width: 200px) {
+ @media (max-width: 400px) {
+ .testmin-\[200px\]\:testmax-\[400px\]\:underline {
+ text-decoration-line: underline;
}
}
- @media (min-width: 200px) {
- @media (max-width: 400px) {
- .testmin-\[200px\]\:testmax-\[400px\]\:underline {
- text-decoration-line: underline;
- }
- }
-
- @media (max-width: 300px) {
- .testmin-\[200px\]\:testmax-\[300px\]\:underline {
- text-decoration-line: underline;
- }
+ @media (max-width: 300px) {
+ .testmin-\[200px\]\:testmax-\[300px\]\:underline {
+ text-decoration-line: underline;
}
}
- `)
- })
- })
-
- it('should default to the DEFAULT value for variants', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('foo', (value) => `.foo${value} &`, {
- values: {
- DEFAULT: '.bar',
- },
- })
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo.bar .foo\:underline {
- text-decoration-line: underline;
- }
- `)
- })
- })
-
- it('should not generate anything if the matchVariant does not have a DEFAULT value configured', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('foo', (value) => `.foo${value} &`)
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css``)
- })
- })
-
- it('should be possible to use `null` as a DEFAULT value', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('foo', (value) => `.foo${value === null ? '-good' : '-bad'} &`, {
- values: { DEFAULT: null },
- })
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo-good .foo\:underline {
- text-decoration-line: underline;
- }
- `)
- })
- })
-
- it('should be possible to use `undefined` as a DEFAULT value', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('foo', (value) => `.foo${value === undefined ? '-good' : '-bad'} &`, {
- values: { DEFAULT: undefined },
- })
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo-good .foo\:underline {
- text-decoration-line: underline;
- }
- `)
- })
- })
-
- it('should be possible to use `undefined` as a DEFAULT value', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- plugins: [
- ({ matchVariant }) => {
- matchVariant('foo', (value) => `.foo${value === undefined ? '-good' : '-bad'} &`, {
- values: { DEFAULT: undefined },
- })
- },
- ],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .foo-good .foo\:underline {
- text-decoration-line: underline;
- }
- `)
- })
- })
-
- it('should not break things', () => {
- let config = {}
-
- let context = createContext(resolveConfig(config))
- let [[, fn]] = context.variantMap.get('group')
-
- let format
-
- expect(
- fn({
- format(input) {
- format = input
- },
- })
- ).toBe(undefined)
-
- expect(format).toBe(':merge(.group) &')
+ }
+ `)
})
})
+
+it('should default to the DEFAULT value for variants', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('foo', (value) => `.foo${value} &`, {
+ values: {
+ DEFAULT: '.bar',
+ },
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo.bar .foo\:underline {
+ text-decoration-line: underline;
+ }
+ `)
+ })
+})
+
+it('should not generate anything if the matchVariant does not have a DEFAULT value configured', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('foo', (value) => `.foo${value} &`)
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css``)
+ })
+})
+
+it('should be possible to use `null` as a DEFAULT value', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('foo', (value) => `.foo${value === null ? '-good' : '-bad'} &`, {
+ values: { DEFAULT: null },
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo-good .foo\:underline {
+ text-decoration-line: underline;
+ }
+ `)
+ })
+})
+
+it('should be possible to use `undefined` as a DEFAULT value', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('foo', (value) => `.foo${value === undefined ? '-good' : '-bad'} &`, {
+ values: { DEFAULT: undefined },
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo-good .foo\:underline {
+ text-decoration-line: underline;
+ }
+ `)
+ })
+})
+
+it('should be possible to use `undefined` as a DEFAULT value', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ ({ matchVariant }) => {
+ matchVariant('foo', (value) => `.foo${value === undefined ? '-good' : '-bad'} &`, {
+ values: { DEFAULT: undefined },
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .foo-good .foo\:underline {
+ text-decoration-line: underline;
+ }
+ `)
+ })
+})
+
+it('should not break things', () => {
+ let config = {}
+
+ let context = createContext(resolveConfig(config))
+ let [[, fn]] = context.variantMap.get('group')
+
+ let format
+
+ expect(
+ fn({
+ format(input) {
+ format = input
+ },
+ })
+ ).toBe(undefined)
+
+ expect(format).toBe(':merge(.group) &')
+})
diff --git a/tests/min-max-screen-variants.test.js b/tests/min-max-screen-variants.test.js
index a86c6f6cd..beab47ac0 100644
--- a/tests/min-max-screen-variants.test.js
+++ b/tests/min-max-screen-variants.test.js
@@ -1,656 +1,642 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(() => {
- let defaultScreens = {
- sm: '640px',
- md: '768px',
- lg: '1024px',
- xl: '1280px',
- '2xl': '1536px',
+let defaultScreens = {
+ sm: '640px',
+ md: '768px',
+ lg: '1024px',
+ xl: '1280px',
+ '2xl': '1536px',
+}
+
+it('sorts min and max correctly relative to screens and each other', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: defaultScreens,
+ },
}
- it('sorts min and max correctly relative to screens and each other', async () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: defaultScreens,
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- @media (max-width: 800px) {
- .max-\[800px\]\:font-bold {
- font-weight: 700;
- }
- }
- @media (max-width: 700px) {
- .max-\[700px\]\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 640px) {
- .sm\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 700px) {
- .min-\[700px\]\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 768px) {
- .md\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 800px) {
- .min-\[800px\]\:font-bold {
- font-weight: 700;
- }
- }
- `)
- })
- })
-
- it('works when using min variants screens config is empty and variants all use the same unit', async () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {},
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
+ let input = css`
+ @tailwind utilities;
+ `
+ return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-bold {
font-weight: 700;
}
+ @media (max-width: 800px) {
+ .max-\[800px\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (max-width: 700px) {
+ .max-\[700px\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 640px) {
+ .sm\:font-bold {
+ font-weight: 700;
+ }
+ }
@media (min-width: 700px) {
.min-\[700px\]\:font-bold {
font-weight: 700;
}
}
+ @media (min-width: 768px) {
+ .md\:font-bold {
+ font-weight: 700;
+ }
+ }
@media (min-width: 800px) {
.min-\[800px\]\:font-bold {
font-weight: 700;
}
}
`)
-
- expect().not.toHaveBeenWarned()
})
+})
- it('works when using max variants screens config is empty and variants all use the same unit', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {},
+it('works when using min variants screens config is empty and variants all use the same unit', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
},
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {},
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
}
+ @media (min-width: 700px) {
+ .min-\[700px\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 800px) {
+ .min-\[800px\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
- let input = css`
- @tailwind utilities;
- `
+ expect().not.toHaveBeenWarned()
+})
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- @media (max-width: 800px) {
- .max-\[800px\]\:font-bold {
- font-weight: 700;
- }
- }
- @media (max-width: 700px) {
- .max-\[700px\]\:font-bold {
- font-weight: 700;
- }
- }
- `)
- })
- })
-
- it('converts simple min-width screens for max variant', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: defaultScreens,
+it('works when using max variants screens config is empty and variants all use the same unit', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
},
- }
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {},
+ },
+ }
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- @media not all and (min-width: 1024px) {
- .max-lg\:font-bold {
- font-weight: 700;
- }
- }
- @media (max-width: 700px) {
- .max-\[700px\]\:font-bold {
- font-weight: 700;
- }
- }
- @media not all and (min-width: 640px) {
- .max-sm\:font-bold {
- font-weight: 700;
- }
- }
- @media (max-width: 300px) {
- .max-\[300px\]\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 640px) {
- .sm\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 768px) {
- .md\:font-bold {
- font-weight: 700;
- }
- }
- `)
- })
- })
-
- it('does not have keyed screens for min variant', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: defaultScreens,
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- @media (min-width: 300px) {
- .min-\[300px\]\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 640px) {
- .sm\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 700px) {
- .min-\[700px\]\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 768px) {
- .md\:font-bold {
- font-weight: 700;
- }
- }
- `)
- })
- })
-
- it('warns when using min variants with complex screen configs', async () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {
- sm: '640px',
- md: '768px',
- lg: '1024px',
- xl: '1280px',
- '2xl': '1536px',
-
- // Any presence of an object makes it complex
- yodawg: { min: '700px' },
- },
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
+ let input = css`
+ @tailwind utilities;
+ `
+ return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-bold {
font-weight: 700;
}
- @media (min-width: 640px) {
- .sm\:font-bold {
+ @media (max-width: 800px) {
+ .max-\[800px\]\:font-bold {
font-weight: 700;
}
}
- @media (min-width: 768px) {
- .md\:font-bold {
- font-weight: 700;
- }
- }
- `)
-
- expect().toHaveBeenWarnedWith(['complex-screen-config'])
- })
-
- it('warns when using min variants with simple configs containing mixed units', async () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {
- sm: '640px',
- md: '48rem',
- lg: '1024px',
- xl: '1280px',
- '2xl': '1536px',
- },
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- @media (min-width: 640px) {
- .sm\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 48rem) {
- .md\:font-bold {
- font-weight: 700;
- }
- }
- `)
-
- expect().toHaveBeenWarnedWith(['mixed-screen-units'])
- })
-
- it('warns when using min variants with mixed units (with screens config)', async () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {
- sm: '640px',
- md: '768px',
- lg: '1024px',
- xl: '1280px',
- '2xl': '1536px',
- },
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- @media (min-width: 640px) {
- .sm\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 768px) {
- .md\:font-bold {
- font-weight: 700;
- }
- }
- `)
-
- expect().toHaveBeenWarnedWith(['minmax-have-mixed-units'])
- })
-
- it('warns when using min variants with mixed units (with no screens config)', async () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {},
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- @media (min-width: 700px) {
- .min-\[700px\]\:font-bold {
- font-weight: 700;
- }
- }
- `)
-
- expect().toHaveBeenWarnedWith(['minmax-have-mixed-units'])
- })
-
- it('warns when using max variants with complex screen configs', async () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {
- sm: '640px',
- md: '768px',
- lg: '1024px',
- xl: '1280px',
- '2xl': '1536px',
-
- // Any presence of an object makes it complex
- yodawg: { min: '700px' },
- },
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- @media (min-width: 640px) {
- .sm\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 768px) {
- .md\:font-bold {
- font-weight: 700;
- }
- }
- `)
-
- expect().toHaveBeenWarnedWith(['complex-screen-config'])
- })
-
- it('warns when using max variants with simple configs containing mixed units', async () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {
- sm: '640px',
- md: '48rem',
- lg: '1024px',
- xl: '1280px',
- '2xl': '1536px',
- },
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- @media (min-width: 640px) {
- .sm\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 48rem) {
- .md\:font-bold {
- font-weight: 700;
- }
- }
- `)
-
- expect().toHaveBeenWarnedWith(['mixed-screen-units'])
- })
-
- it('warns when using max variants with mixed units (with screens config)', async () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {
- sm: '640px',
- md: '768px',
- lg: '1024px',
- xl: '1280px',
- '2xl': '1536px',
- },
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- @media (min-width: 640px) {
- .sm\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 768px) {
- .md\:font-bold {
- font-weight: 700;
- }
- }
- `)
-
- expect().toHaveBeenWarnedWith(['minmax-have-mixed-units'])
- })
-
- it('warns when using max variants with mixed units (with no screens config)', async () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {},
- },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
@media (max-width: 700px) {
.max-\[700px\]\:font-bold {
font-weight: 700;
}
}
`)
-
- expect().toHaveBeenWarnedWith(['minmax-have-mixed-units'])
})
+})
- it('warns when using min and max variants with mixed units (with no screens config)', async () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- screens: {},
+it('converts simple min-width screens for max variant', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
},
- }
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: defaultScreens,
+ },
+ }
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
+ let input = css`
+ @tailwind utilities;
+ `
+ return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
.font-bold {
font-weight: 700;
}
- @media (max-width: 700rem) {
- .max-\[700rem\]\:font-bold {
+ @media not all and (min-width: 1024px) {
+ .max-lg\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (max-width: 700px) {
+ .max-\[700px\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media not all and (min-width: 640px) {
+ .max-sm\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (max-width: 300px) {
+ .max-\[300px\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 640px) {
+ .sm\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 768px) {
+ .md\:font-bold {
font-weight: 700;
}
}
`)
-
- expect().toHaveBeenWarnedWith(['minmax-have-mixed-units'])
})
})
+
+it('does not have keyed screens for min variant', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: defaultScreens,
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ @media (min-width: 300px) {
+ .min-\[300px\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 640px) {
+ .sm\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 700px) {
+ .min-\[700px\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 768px) {
+ .md\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+ })
+})
+
+it('warns when using min variants with complex screen configs', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {
+ sm: '640px',
+ md: '768px',
+ lg: '1024px',
+ xl: '1280px',
+ '2xl': '1536px',
+
+ // Any presence of an object makes it complex
+ yodawg: { min: '700px' },
+ },
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ @media (min-width: 640px) {
+ .sm\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 768px) {
+ .md\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+
+ expect().toHaveBeenWarnedWith(['complex-screen-config'])
+})
+
+it('warns when using min variants with simple configs containing mixed units', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {
+ sm: '640px',
+ md: '48rem',
+ lg: '1024px',
+ xl: '1280px',
+ '2xl': '1536px',
+ },
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ @media (min-width: 640px) {
+ .sm\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 48rem) {
+ .md\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+
+ expect().toHaveBeenWarnedWith(['mixed-screen-units'])
+})
+
+it('warns when using min variants with mixed units (with screens config)', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {
+ sm: '640px',
+ md: '768px',
+ lg: '1024px',
+ xl: '1280px',
+ '2xl': '1536px',
+ },
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ @media (min-width: 640px) {
+ .sm\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 768px) {
+ .md\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+
+ expect().toHaveBeenWarnedWith(['minmax-have-mixed-units'])
+})
+
+it('warns when using min variants with mixed units (with no screens config)', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {},
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ @media (min-width: 700px) {
+ .min-\[700px\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+
+ expect().toHaveBeenWarnedWith(['minmax-have-mixed-units'])
+})
+
+it('warns when using max variants with complex screen configs', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {
+ sm: '640px',
+ md: '768px',
+ lg: '1024px',
+ xl: '1280px',
+ '2xl': '1536px',
+
+ // Any presence of an object makes it complex
+ yodawg: { min: '700px' },
+ },
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ @media (min-width: 640px) {
+ .sm\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 768px) {
+ .md\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+
+ expect().toHaveBeenWarnedWith(['complex-screen-config'])
+})
+
+it('warns when using max variants with simple configs containing mixed units', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {
+ sm: '640px',
+ md: '48rem',
+ lg: '1024px',
+ xl: '1280px',
+ '2xl': '1536px',
+ },
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ @media (min-width: 640px) {
+ .sm\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 48rem) {
+ .md\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+
+ expect().toHaveBeenWarnedWith(['mixed-screen-units'])
+})
+
+it('warns when using max variants with mixed units (with screens config)', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {
+ sm: '640px',
+ md: '768px',
+ lg: '1024px',
+ xl: '1280px',
+ '2xl': '1536px',
+ },
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ @media (min-width: 640px) {
+ .sm\:font-bold {
+ font-weight: 700;
+ }
+ }
+ @media (min-width: 768px) {
+ .md\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+
+ expect().toHaveBeenWarnedWith(['minmax-have-mixed-units'])
+})
+
+it('warns when using max variants with mixed units (with no screens config)', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {},
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ @media (max-width: 700px) {
+ .max-\[700px\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+
+ expect().toHaveBeenWarnedWith(['minmax-have-mixed-units'])
+})
+
+it('warns when using min and max variants with mixed units (with no screens config)', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ screens: {},
+ },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ @media (max-width: 700rem) {
+ .max-\[700rem\]\:font-bold {
+ font-weight: 700;
+ }
+ }
+ `)
+
+ expect().toHaveBeenWarnedWith(['minmax-have-mixed-units'])
+})
diff --git a/tests/minimum-impact-selector.test.js b/tests/minimum-impact-selector.test.js
index ed2fad914..d8292e467 100644
--- a/tests/minimum-impact-selector.test.js
+++ b/tests/minimum-impact-selector.test.js
@@ -1,32 +1,29 @@
import { elementSelectorParser } from '../src/lib/resolveDefaultsAtRules'
-import { crosscheck } from './util/run'
-crosscheck(() => {
- it.each`
- before | after
- ${'*'} | ${'*'}
- ${'*:hover'} | ${'*'}
- ${'* > *'} | ${'* > *'}
- ${'.foo'} | ${'.foo'}
- ${'.foo:hover'} | ${'.foo'}
- ${'.foo:focus:hover'} | ${'.foo'}
- ${'li:first-child'} | ${'li'}
- ${'li:before'} | ${'li:before'}
- ${'li::before'} | ${'li::before'}
- ${'#app .foo'} | ${'.foo'}
- ${'#app'} | ${'[id=app]'}
- ${'#app.other'} | ${'.other'}
- ${'input[type="text"]'} | ${'[type="text"]'}
- ${'input[type="text"].foo'} | ${'.foo'}
- ${'.group .group\\:foo'} | ${'.group\\:foo'}
- ${'.group:hover .group-hover\\:foo'} | ${'.group-hover\\:foo'}
- ${'.owl > * + *'} | ${'.owl > *'}
- ${'.owl > :not([hidden]) + :not([hidden])'} | ${'.owl > *'}
- ${'.group:hover .group-hover\\:owl > :not([hidden]) + :not([hidden])'} | ${'.group-hover\\:owl > *'}
- ${'.peer:first-child ~ .peer-first\\:shadow-md'} | ${'.peer-first\\:shadow-md'}
- ${'.whats ~ .next > span:hover'} | ${'span'}
- ${'.foo .bar ~ .baz > .next > span > article:hover'} | ${'article'}
- `('should generate "$after" from "$before"', ({ before, after }) => {
- expect(elementSelectorParser.transformSync(before).join(', ')).toEqual(after)
- })
+it.each`
+ before | after
+ ${'*'} | ${'*'}
+ ${'*:hover'} | ${'*'}
+ ${'* > *'} | ${'* > *'}
+ ${'.foo'} | ${'.foo'}
+ ${'.foo:hover'} | ${'.foo'}
+ ${'.foo:focus:hover'} | ${'.foo'}
+ ${'li:first-child'} | ${'li'}
+ ${'li:before'} | ${'li:before'}
+ ${'li::before'} | ${'li::before'}
+ ${'#app .foo'} | ${'.foo'}
+ ${'#app'} | ${'[id=app]'}
+ ${'#app.other'} | ${'.other'}
+ ${'input[type="text"]'} | ${'[type="text"]'}
+ ${'input[type="text"].foo'} | ${'.foo'}
+ ${'.group .group\\:foo'} | ${'.group\\:foo'}
+ ${'.group:hover .group-hover\\:foo'} | ${'.group-hover\\:foo'}
+ ${'.owl > * + *'} | ${'.owl > *'}
+ ${'.owl > :not([hidden]) + :not([hidden])'} | ${'.owl > *'}
+ ${'.group:hover .group-hover\\:owl > :not([hidden]) + :not([hidden])'} | ${'.group-hover\\:owl > *'}
+ ${'.peer:first-child ~ .peer-first\\:shadow-md'} | ${'.peer-first\\:shadow-md'}
+ ${'.whats ~ .next > span:hover'} | ${'span'}
+ ${'.foo .bar ~ .baz > .next > span > article:hover'} | ${'article'}
+`('should generate "$after" from "$before"', ({ before, after }) => {
+ expect(elementSelectorParser.transformSync(before).join(', ')).toEqual(after)
})
diff --git a/tests/modify-selectors.test.js b/tests/modify-selectors.test.js
index 3176b17f3..e6ce3cf14 100644
--- a/tests/modify-selectors.test.js
+++ b/tests/modify-selectors.test.js
@@ -1,95 +1,93 @@
import selectorParser from 'postcss-selector-parser'
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(() => {
- test('modify selectors', () => {
- let config = {
- darkMode: 'class',
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
Lorem ipsum dolor sit amet...
-
-
-
Lorem ipsum dolor sit amet...
-
-
-
Lorem ipsum dolor sit amet...
-
-
-
Lorem ipsum dolor sit amet...
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {},
- plugins: [
- function ({ addVariant }) {
- addVariant('foo', ({ modifySelectors, separator }) => {
- modifySelectors(({ selector }) => {
- return selectorParser((selectors) => {
- selectors.walkClasses((classNode) => {
- classNode.value = `foo${separator}${classNode.value}`
- classNode.parent.insertBefore(classNode, selectorParser().astSync(`.foo `))
- })
- }).processSync(selector)
- })
+test('modify selectors', () => {
+ let config = {
+ darkMode: 'class',
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
Lorem ipsum dolor sit amet...
+
+
+
Lorem ipsum dolor sit amet...
+
+
+
Lorem ipsum dolor sit amet...
+
+
+
Lorem ipsum dolor sit amet...
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {},
+ plugins: [
+ function ({ addVariant }) {
+ addVariant('foo', ({ modifySelectors, separator }) => {
+ modifySelectors(({ selector }) => {
+ return selectorParser((selectors) => {
+ selectors.walkClasses((classNode) => {
+ classNode.value = `foo${separator}${classNode.value}`
+ classNode.parent.insertBefore(classNode, selectorParser().astSync(`.foo `))
+ })
+ }).processSync(selector)
})
- },
- ],
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind components;
+ @tailwind utilities;
+
+ @layer components {
+ .markdown > p {
+ margin-top: 12px;
+ }
}
+ `
- let input = css`
- @tailwind components;
- @tailwind utilities;
-
- @layer components {
- .markdown > p {
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .markdown > p {
+ margin-top: 12px;
+ }
+ .font-bold {
+ font-weight: 700;
+ }
+ .foo .foo\:markdown > p,
+ .foo .foo\:visited\:markdown:visited > p {
+ margin-top: 12px;
+ }
+ @media (min-width: 1024px) {
+ .foo .lg\:foo\:disabled\:markdown:disabled > p {
margin-top: 12px;
}
}
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .markdown > p {
- margin-top: 12px;
- }
- .font-bold {
+ .foo .foo\:font-bold,
+ .foo .foo\:hover\:font-bold:hover {
+ font-weight: 700;
+ }
+ @media (min-width: 640px) {
+ .foo .sm\:foo\:font-bold {
font-weight: 700;
}
- .foo .foo\:markdown > p,
- .foo .foo\:visited\:markdown:visited > p {
- margin-top: 12px;
- }
- @media (min-width: 1024px) {
- .foo .lg\:foo\:disabled\:markdown:disabled > p {
- margin-top: 12px;
- }
- }
- .foo .foo\:font-bold,
- .foo .foo\:hover\:font-bold:hover {
+ }
+ @media (min-width: 768px) {
+ .foo .md\:foo\:focus\:font-bold:focus {
font-weight: 700;
}
- @media (min-width: 640px) {
- .foo .sm\:foo\:font-bold {
- font-weight: 700;
- }
- }
- @media (min-width: 768px) {
- .foo .md\:foo\:focus\:font-bold:focus {
- font-weight: 700;
- }
- }
- `)
- })
+ }
+ `)
})
})
diff --git a/tests/mutable.test.js b/tests/mutable.test.js
index 45419380f..5c3f9a487 100644
--- a/tests/mutable.test.js
+++ b/tests/mutable.test.js
@@ -1,54 +1,52 @@
import path from 'path'
import postcss from 'postcss'
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(() => {
- function pluginThatMutatesRules() {
- return (root) => {
- root.walkRules((rule) => {
- rule.nodes
- .filter((node) => node.prop === 'background-image')
- .forEach((node) => {
- node.value = 'url("./bar.png")'
- })
+function pluginThatMutatesRules() {
+ return (root) => {
+ root.walkRules((rule) => {
+ rule.nodes
+ .filter((node) => node.prop === 'background-image')
+ .forEach((node) => {
+ node.value = 'url("./bar.png")'
+ })
- return rule
- })
- }
+ return rule
+ })
+ }
+}
+
+test('plugins mutating rules after tailwind doesnt break it', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ backgroundImage: {
+ foo: 'url("./foo.png")',
+ },
+ },
+ plugins: [],
}
- test('plugins mutating rules after tailwind doesnt break it', async () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- backgroundImage: {
- foo: 'url("./foo.png")',
- },
- },
- plugins: [],
- }
+ function checkResult(result) {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-foo {
+ background-image: url('./foo.png');
+ }
+ `)
+ }
- function checkResult(result) {
- expect(result.css).toMatchFormattedCss(css`
- .bg-foo {
- background-image: url('./foo.png');
- }
- `)
- }
+ // Verify the first run produces the expected result
+ let firstRun = await run('@tailwind utilities', config)
+ checkResult(firstRun)
- // Verify the first run produces the expected result
- let firstRun = await run('@tailwind utilities', config)
- checkResult(firstRun)
-
- // Outside of the context of tailwind jit more postcss plugins may operate on the AST:
- // In this case we have a plugin that mutates rules directly
- await postcss([pluginThatMutatesRules()]).process(firstRun, {
- from: path.resolve(__filename),
- })
-
- // Verify subsequent runs don't produce mutated rules
- let secondRun = await run('@tailwind utilities', config)
- checkResult(secondRun)
+ // Outside of the context of tailwind jit more postcss plugins may operate on the AST:
+ // In this case we have a plugin that mutates rules directly
+ await postcss([pluginThatMutatesRules()]).process(firstRun, {
+ from: path.resolve(__filename),
})
+
+ // Verify subsequent runs don't produce mutated rules
+ let secondRun = await run('@tailwind utilities', config)
+ checkResult(secondRun)
})
diff --git a/tests/negateValue.test.js b/tests/negateValue.test.js
index 052132f91..c31741d48 100644
--- a/tests/negateValue.test.js
+++ b/tests/negateValue.test.js
@@ -1,17 +1,14 @@
import negateValue from '../src/util/negateValue'
-import { crosscheck } from './util/run'
-crosscheck(() => {
- test('it negates numeric CSS values', () => {
- expect(negateValue('5')).toEqual('-5')
- expect(negateValue('10px')).toEqual('-10px')
- expect(negateValue('18rem')).toEqual('-18rem')
- expect(negateValue('-10')).toEqual('10')
- expect(negateValue('-7ch')).toEqual('7ch')
- })
-
- test('values that cannot be negated become undefined', () => {
- expect(negateValue('auto')).toBeUndefined()
- expect(negateValue('cover')).toBeUndefined()
- })
+test('it negates numeric CSS values', () => {
+ expect(negateValue('5')).toEqual('-5')
+ expect(negateValue('10px')).toEqual('-10px')
+ expect(negateValue('18rem')).toEqual('-18rem')
+ expect(negateValue('-10')).toEqual('10')
+ expect(negateValue('-7ch')).toEqual('7ch')
+})
+
+test('values that cannot be negated become undefined', () => {
+ expect(negateValue('auto')).toBeUndefined()
+ expect(negateValue('cover')).toBeUndefined()
})
diff --git a/tests/negated-content.test.js b/tests/negated-content.test.js
index 66e231946..6d46e3a8d 100644
--- a/tests/negated-content.test.js
+++ b/tests/negated-content.test.js
@@ -1,29 +1,27 @@
import * as path from 'path'
-import { crosscheck, run, css, defaults } from './util/run'
+import { run, css, defaults } from './util/run'
-crosscheck(() => {
- it('should be possible to use negated content patterns', () => {
- let config = {
- content: [
- path.resolve(__dirname, './negated-content-*.test.html'),
- '!' + path.resolve(__dirname, './negated-content-ignore.test.html'),
- ],
- corePlugins: { preflight: false },
- }
+it('should be possible to use negated content patterns', () => {
+ let config = {
+ content: [
+ path.resolve(__dirname, './negated-content-*.test.html'),
+ '!' + path.resolve(__dirname, './negated-content-ignore.test.html'),
+ ],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- .uppercase {
- text-transform: uppercase;
- }
- `)
- })
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults}
+ .uppercase {
+ text-transform: uppercase;
+ }
+ `)
})
})
diff --git a/tests/negative-prefix.test.js b/tests/negative-prefix.test.js
index c799bb54e..615558807 100644
--- a/tests/negative-prefix.test.js
+++ b/tests/negative-prefix.test.js
@@ -1,346 +1,342 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(() => {
- test('using a negative prefix with a negative scale value', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- margin: {
- 2: '8px',
- '-2': '-4px',
- },
+test('using a negative prefix with a negative scale value', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ margin: {
+ 2: '8px',
+ '-2': '-4px',
},
- }
+ },
+ }
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .-mt-2 {
- margin-top: -4px;
- }
- .mt-2 {
- margin-top: 8px;
- }
- `)
- })
- })
-
- test('using a negative scale value with a plugin that does not support dynamic negative values', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- opacity: {
- '-50': '0.5',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .-opacity-50 {
- opacity: 0.5;
- }
- `)
- })
- })
-
- test('using a negative prefix without a negative scale value', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- margin: {
- 5: '20px',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .-mt-5 {
- margin-top: -20px;
- }
- .mt-5 {
- margin-top: 20px;
- }
- `)
- })
- })
-
- test('being an asshole', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- margin: {
- '-[10px]': '55px',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .-mt-\[10px\] {
- margin-top: 55px;
- }
- `)
- })
- })
-
- test('being a real asshole', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- margin: {
- '[10px]': '55px',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .-mt-\[10px\] {
- margin-top: -55px;
- }
- `)
- })
- })
-
- test('a value that includes a variable', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- margin: {
- 5: 'var(--sizing-5)',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .-mt-5 {
- margin-top: calc(var(--sizing-5) * -1);
- }
- .mt-5 {
- margin-top: var(--sizing-5);
- }
- `)
- })
- })
-
- test('a value that includes a calc', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- margin: {
- 5: 'calc(52px * -3)',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .-mt-5 {
- margin-top: 156px;
- }
- .mt-5 {
- margin-top: -156px;
- }
- `)
- })
- })
-
- test('a value that includes min/max/clamp functions', () => {
- let config = {
- content: [
- { raw: html`
` },
- ],
- theme: {
- margin: {
- min: 'min(100vmin, 3rem)',
- max: 'max(100vmax, 3rem)',
- clamp: 'clamp(1rem, 100vh, 3rem)',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .-mt-clamp {
- margin-top: calc(-1 * clamp(1rem, 100vh, 3rem));
- }
- .-mt-max {
- margin-top: calc(-1 * max(100vmax, 3rem));
- }
- .-mt-min {
- margin-top: calc(-1 * min(100vmin, 3rem));
- }
- .mt-clamp {
- margin-top: clamp(1rem, 100vh, 3rem);
- }
- .mt-max {
- margin-top: max(100vmax, 3rem);
- }
- .mt-min {
- margin-top: min(100vmin, 3rem);
- }
- `)
- })
- })
-
- test('a keyword value', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- margin: {
- auto: 'auto',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .mt-auto {
- margin-top: auto;
- }
- `)
- })
- })
-
- test('a zero value', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- margin: {
- 0: '0',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .-mt-0,
- .mt-0 {
- margin-top: 0;
- }
- `)
- })
- })
-
- test('a color', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- colors: {
- red: 'red',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css``)
- })
- })
-
- test('arbitrary values', () => {
- let config = {
- content: [{ raw: html`
` }],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .-mt-\[10px\] {
- margin-top: -10px;
- }
- `)
- })
- })
-
- test('negating a negative scale value', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- margin: {
- weird: '-15px',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .-mt-weird {
- margin-top: 15px;
- }
- `)
- })
- })
-
- test('negating a default value', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- margin: {
- DEFAULT: '15px',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .-mt {
- margin-top: -15px;
- }
- `)
- })
- })
-
- test('using a negative prefix with a negative default scale value', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- margin: {
- DEFAULT: '8px',
- '-DEFAULT': '-4px',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .-mt {
- margin-top: -4px;
- }
- .mt {
- margin-top: 8px;
- }
- `)
- })
- })
-
- test('negating a default value with a configured prefix', () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`
` }],
- theme: {
- margin: {
- DEFAULT: '15px',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css`
- .tw--mt {
- margin-top: -15px;
- }
- `)
- })
- })
-
- test('arbitrary value keywords should be ignored', () => {
- let config = {
- content: [{ raw: html`
` }],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchCss(css``)
- })
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .-mt-2 {
+ margin-top: -4px;
+ }
+ .mt-2 {
+ margin-top: 8px;
+ }
+ `)
+ })
+})
+
+test('using a negative scale value with a plugin that does not support dynamic negative values', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ opacity: {
+ '-50': '0.5',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .-opacity-50 {
+ opacity: 0.5;
+ }
+ `)
+ })
+})
+
+test('using a negative prefix without a negative scale value', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ margin: {
+ 5: '20px',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .-mt-5 {
+ margin-top: -20px;
+ }
+ .mt-5 {
+ margin-top: 20px;
+ }
+ `)
+ })
+})
+
+test('being an asshole', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ margin: {
+ '-[10px]': '55px',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .-mt-\[10px\] {
+ margin-top: 55px;
+ }
+ `)
+ })
+})
+
+test('being a real asshole', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ margin: {
+ '[10px]': '55px',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .-mt-\[10px\] {
+ margin-top: -55px;
+ }
+ `)
+ })
+})
+
+test('a value that includes a variable', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ margin: {
+ 5: 'var(--sizing-5)',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .-mt-5 {
+ margin-top: calc(var(--sizing-5) * -1);
+ }
+ .mt-5 {
+ margin-top: var(--sizing-5);
+ }
+ `)
+ })
+})
+
+test('a value that includes a calc', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ margin: {
+ 5: 'calc(52px * -3)',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .-mt-5 {
+ margin-top: 156px;
+ }
+ .mt-5 {
+ margin-top: -156px;
+ }
+ `)
+ })
+})
+
+test('a value that includes min/max/clamp functions', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ margin: {
+ min: 'min(100vmin, 3rem)',
+ max: 'max(100vmax, 3rem)',
+ clamp: 'clamp(1rem, 100vh, 3rem)',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .-mt-clamp {
+ margin-top: calc(-1 * clamp(1rem, 100vh, 3rem));
+ }
+ .-mt-max {
+ margin-top: calc(-1 * max(100vmax, 3rem));
+ }
+ .-mt-min {
+ margin-top: calc(-1 * min(100vmin, 3rem));
+ }
+ .mt-clamp {
+ margin-top: clamp(1rem, 100vh, 3rem);
+ }
+ .mt-max {
+ margin-top: max(100vmax, 3rem);
+ }
+ .mt-min {
+ margin-top: min(100vmin, 3rem);
+ }
+ `)
+ })
+})
+
+test('a keyword value', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ margin: {
+ auto: 'auto',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .mt-auto {
+ margin-top: auto;
+ }
+ `)
+ })
+})
+
+test('a zero value', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ margin: {
+ 0: '0',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .-mt-0,
+ .mt-0 {
+ margin-top: 0;
+ }
+ `)
+ })
+})
+
+test('a color', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ colors: {
+ red: 'red',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css``)
+ })
+})
+
+test('arbitrary values', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .-mt-\[10px\] {
+ margin-top: -10px;
+ }
+ `)
+ })
+})
+
+test('negating a negative scale value', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ margin: {
+ weird: '-15px',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .-mt-weird {
+ margin-top: 15px;
+ }
+ `)
+ })
+})
+
+test('negating a default value', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ margin: {
+ DEFAULT: '15px',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .-mt {
+ margin-top: -15px;
+ }
+ `)
+ })
+})
+
+test('using a negative prefix with a negative default scale value', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ margin: {
+ DEFAULT: '8px',
+ '-DEFAULT': '-4px',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .-mt {
+ margin-top: -4px;
+ }
+ .mt {
+ margin-top: 8px;
+ }
+ `)
+ })
+})
+
+test('negating a default value with a configured prefix', () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`
` }],
+ theme: {
+ margin: {
+ DEFAULT: '15px',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .tw--mt {
+ margin-top: -15px;
+ }
+ `)
+ })
+})
+
+test('arbitrary value keywords should be ignored', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css``)
})
})
diff --git a/tests/normalize-config.test.js b/tests/normalize-config.test.js
index a0b7a59bb..0468c8c78 100644
--- a/tests/normalize-config.test.js
+++ b/tests/normalize-config.test.js
@@ -1,164 +1,155 @@
import { normalizeConfig } from '../src/util/normalizeConfig'
import resolveConfig from '../src/public/resolve-config'
-import { crosscheck, run, css } from './util/run'
+import { run, css } from './util/run'
-crosscheck(({ stable, oxide }) => {
- it.each`
- config
- ${{ purge: [{ raw: 'text-center' }] }}
- ${{ purge: { content: [{ raw: 'text-center' }] } }}
- ${{ content: { content: [{ raw: 'text-center' }] } }}
- `('should normalize content $config', ({ config }) => {
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .text-center {
- text-align: center;
- }
- `)
- })
- })
-
- it.each`
- config
- ${{ purge: { safelist: ['text-center'] } }}
- ${{ purge: { options: { safelist: ['text-center'] } } }}
- ${{ content: { safelist: ['text-center'] } }}
- `('should normalize safelist $config', ({ config }) => {
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .text-center {
- text-align: center;
- }
- `)
- })
- })
-
- oxide.test.todo('should normalize extractors')
- stable.test.each`
- config
- ${{ content: [{ raw: 'text-center' }], purge: { extract: () => ['font-bold'] } }}
- ${{ content: [{ raw: 'text-center' }], purge: { extract: { DEFAULT: () => ['font-bold'] } } }}
- ${{
- content: [{ raw: 'text-center' }],
- purge: { options: { defaultExtractor: () => ['font-bold'] } },
- }}
- ${{
- content: [{ raw: 'text-center' }],
- purge: {
- options: { extractors: [{ extractor: () => ['font-bold'], extensions: ['html'] }] },
- },
- }}
- ${{ content: [{ raw: 'text-center' }], purge: { extract: { html: () => ['font-bold'] } } }}
- `('should normalize extractors $config', ({ config }) => {
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .font-bold {
- font-weight: 700;
- }
- `)
- })
- })
-
- oxide.test.todo('should still be possible to use the "old" v2 config')
- stable.test('should still be possible to use the "old" v2 config', () => {
- let config = {
- purge: {
- content: [
- { raw: 'text-svelte', extension: 'svelte' },
- { raw: '# My Big Heading', extension: 'md' },
- ],
- options: {
- defaultExtractor(content) {
- return content.split(' ').concat(['font-bold'])
- },
- },
- extract: {
- svelte(content) {
- return content.replace('svelte', 'center').split(' ')
- },
- },
- transform: {
- md() {
- return 'text-4xl'
- },
- },
- },
- theme: {
- extends: {},
- },
- variants: {
- extends: {},
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .text-center {
- text-align: center;
- }
- .text-4xl {
- font-size: 2.25rem;
- line-height: 2.5rem;
- }
- .font-bold {
- font-weight: 700;
- }
- `)
- })
- })
-
- it('should keep content files with globs', () => {
- let config = {
- content: ['./example-folder/**/*.{html,js}'],
- }
-
- stable.expect(normalizeConfig(resolveConfig(config)).content).toEqual({
- files: ['./example-folder/**/*.{html,js}'],
- relative: false,
- extract: {},
- transform: {},
- })
- oxide.expect(normalizeConfig(resolveConfig(config)).content).toEqual({
- files: ['./example-folder/**/*.{html,js}'],
- relative: true,
- extract: {},
- transform: {},
- })
- })
-
- it('should warn when we detect invalid globs with incorrect brace expansion', () => {
- let config = {
- content: [
- './{example-folder}/**/*.{html,js}',
- './{example-folder}/**/*.{html}',
- './example-folder/**/*.{html}',
- ],
- }
-
- let normalizedConfig = normalizeConfig(resolveConfig(config)).content
-
- // No rewrite happens
- stable.expect(normalizedConfig).toEqual({
- files: [
- './{example-folder}/**/*.{html,js}',
- './{example-folder}/**/*.{html}',
- './example-folder/**/*.{html}',
- ],
- relative: false,
- extract: {},
- transform: {},
- })
- oxide.expect(normalizedConfig).toEqual({
- files: [
- './{example-folder}/**/*.{html,js}',
- './{example-folder}/**/*.{html}',
- './example-folder/**/*.{html}',
- ],
- relative: true,
- extract: {},
- transform: {},
- })
-
- expect().toHaveBeenWarnedWith(['invalid-glob-braces'])
+it.each`
+ config
+ ${{ purge: [{ raw: 'text-center' }] }}
+ ${{ purge: { content: [{ raw: 'text-center' }] } }}
+ ${{ content: { files: [], content: [{ raw: 'text-center' }] } }}
+`('should normalize content $config', ({ config }) => {
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .text-center {
+ text-align: center;
+ }
+ `)
})
})
+
+it.each`
+ config
+ ${{ purge: { safelist: ['text-center'] } }}
+ ${{ purge: { options: { safelist: ['text-center'] } } }}
+ ${{ content: { files: [], safelist: ['text-center'] } }}
+`('should normalize safelist $config', ({ config }) => {
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .text-center {
+ text-align: center;
+ }
+ `)
+ })
+})
+
+test.each`
+ config
+ ${{
+ purge: { content: [{ raw: 'text-center' }], extract: () => ['font-bold'] },
+}}
+ ${{
+ purge: { content: [{ raw: 'text-center' }], extract: { DEFAULT: () => ['font-bold'] } },
+}}
+ ${{
+ purge: {
+ content: [{ raw: 'text-center' }],
+ options: { defaultExtractor: () => ['font-bold'] },
+ },
+}}
+ ${{
+ purge: {
+ content: [{ raw: 'text-center' }],
+ options: { extractors: [{ extractor: () => ['font-bold'], extensions: ['html'] }] },
+ },
+}}
+ ${{
+ purge: {
+ content: [{ raw: 'text-center' }],
+ extract: { html: () => ['font-bold'] },
+ },
+}}
+`('should normalize extractors $config', ({ config }) => {
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ `)
+ })
+})
+
+test('should still be possible to use the "old" v2 config', () => {
+ let config = {
+ purge: {
+ content: [
+ { raw: 'text-svelte', extension: 'svelte' },
+ { raw: '# My Big Heading', extension: 'md' },
+ ],
+ options: {
+ defaultExtractor(content) {
+ return content.split(' ').concat(['font-bold'])
+ },
+ },
+ extract: {
+ svelte(content) {
+ return content.replace('svelte', 'center').split(' ')
+ },
+ },
+ transform: {
+ md() {
+ return 'text-4xl'
+ },
+ },
+ },
+ theme: {
+ extends: {},
+ },
+ variants: {
+ extends: {},
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .text-center {
+ text-align: center;
+ }
+ .text-4xl {
+ font-size: 2.25rem;
+ line-height: 2.5rem;
+ }
+ .font-bold {
+ font-weight: 700;
+ }
+ `)
+ })
+})
+
+it('should keep content files with globs', () => {
+ let config = {
+ content: ['./example-folder/**/*.{html,js}'],
+ }
+
+ expect(normalizeConfig(resolveConfig(config)).content).toEqual({
+ files: ['./example-folder/**/*.{html,js}'],
+ relative: false,
+ extract: {},
+ transform: {},
+ })
+})
+
+it('should warn when we detect invalid globs with incorrect brace expansion', () => {
+ let config = {
+ content: [
+ './{example-folder}/**/*.{html,js}',
+ './{example-folder}/**/*.{html}',
+ './example-folder/**/*.{html}',
+ ],
+ }
+
+ let normalizedConfig = normalizeConfig(resolveConfig(config)).content
+
+ // No rewrite happens
+ expect(normalizedConfig).toEqual({
+ files: [
+ './{example-folder}/**/*.{html,js}',
+ './{example-folder}/**/*.{html}',
+ './example-folder/**/*.{html}',
+ ],
+ relative: false,
+ extract: {},
+ transform: {},
+ })
+
+ expect().toHaveBeenWarnedWith(['invalid-glob-braces'])
+})
diff --git a/tests/normalize-data-types.test.js b/tests/normalize-data-types.test.js
index eff2fa677..d99ce1c18 100644
--- a/tests/normalize-data-types.test.js
+++ b/tests/normalize-data-types.test.js
@@ -1,5 +1,4 @@
import { normalize } from '../src/util/dataTypes'
-import { crosscheck } from './util/run'
let table = [
['foo', 'foo'],
@@ -47,8 +46,6 @@ let table = [
['color(0_0_0_/_1.0)', 'color(0 0 0 / 1.0)'],
]
-crosscheck(() => {
- it.each(table)('normalize data: %s', (input, output) => {
- expect(normalize(input)).toBe(output)
- })
+it.each(table)('normalize data: %s', (input, output) => {
+ expect(normalize(input)).toBe(output)
})
diff --git a/tests/normalize-screens.test.js b/tests/normalize-screens.test.js
index 464623f9f..da31c50d3 100644
--- a/tests/normalize-screens.test.js
+++ b/tests/normalize-screens.test.js
@@ -1,66 +1,63 @@
import { normalizeScreens } from '../src/util/normalizeScreens'
-import { crosscheck } from './util/run'
-crosscheck(() => {
- it('should normalize an array of string values', () => {
- let screens = ['768px', '1200px']
+it('should normalize an array of string values', () => {
+ let screens = ['768px', '1200px']
- expect(normalizeScreens(screens)).toEqual([
- { name: '768px', not: false, values: [{ min: '768px', max: undefined }] },
- { name: '1200px', not: false, values: [{ min: '1200px', max: undefined }] },
- ])
- })
-
- it('should normalize an object with string values', () => {
- let screens = {
- a: '768px',
- b: '1200px',
- }
-
- expect(normalizeScreens(screens)).toEqual([
- { name: 'a', not: false, values: [{ min: '768px', max: undefined }] },
- { name: 'b', not: false, values: [{ min: '1200px', max: undefined }] },
- ])
- })
-
- it('should normalize an object with object values', () => {
- let screens = {
- a: { min: '768px' },
- b: { max: '1200px' },
- }
-
- expect(normalizeScreens(screens)).toEqual([
- { name: 'a', not: false, values: [{ min: '768px', max: undefined }] },
- { name: 'b', not: false, values: [{ min: undefined, max: '1200px' }] },
- ])
- })
-
- it('should normalize an object with multiple object values', () => {
- let screens = {
- a: [{ min: '768px' }, { max: '1200px' }],
- }
-
- expect(normalizeScreens(screens)).toEqual([
- {
- name: 'a',
- not: false,
- values: [
- { max: undefined, min: '768px', raw: undefined },
- { max: '1200px', min: undefined, raw: undefined },
- ],
- },
- ])
- })
-
- it('should normalize an object with object values (min-width normalized to width)', () => {
- let screens = {
- a: { 'min-width': '768px' },
- b: { max: '1200px' },
- }
-
- expect(normalizeScreens(screens)).toEqual([
- { name: 'a', not: false, values: [{ min: '768px', max: undefined }] },
- { name: 'b', not: false, values: [{ min: undefined, max: '1200px' }] },
- ])
- })
+ expect(normalizeScreens(screens)).toEqual([
+ { name: '768px', not: false, values: [{ min: '768px', max: undefined }] },
+ { name: '1200px', not: false, values: [{ min: '1200px', max: undefined }] },
+ ])
+})
+
+it('should normalize an object with string values', () => {
+ let screens = {
+ a: '768px',
+ b: '1200px',
+ }
+
+ expect(normalizeScreens(screens)).toEqual([
+ { name: 'a', not: false, values: [{ min: '768px', max: undefined }] },
+ { name: 'b', not: false, values: [{ min: '1200px', max: undefined }] },
+ ])
+})
+
+it('should normalize an object with object values', () => {
+ let screens = {
+ a: { min: '768px' },
+ b: { max: '1200px' },
+ }
+
+ expect(normalizeScreens(screens)).toEqual([
+ { name: 'a', not: false, values: [{ min: '768px', max: undefined }] },
+ { name: 'b', not: false, values: [{ min: undefined, max: '1200px' }] },
+ ])
+})
+
+it('should normalize an object with multiple object values', () => {
+ let screens = {
+ a: [{ min: '768px' }, { max: '1200px' }],
+ }
+
+ expect(normalizeScreens(screens)).toEqual([
+ {
+ name: 'a',
+ not: false,
+ values: [
+ { max: undefined, min: '768px', raw: undefined },
+ { max: '1200px', min: undefined, raw: undefined },
+ ],
+ },
+ ])
+})
+
+it('should normalize an object with object values (min-width normalized to width)', () => {
+ let screens = {
+ a: { 'min-width': '768px' },
+ b: { max: '1200px' },
+ }
+
+ expect(normalizeScreens(screens)).toEqual([
+ { name: 'a', not: false, values: [{ min: '768px', max: undefined }] },
+ { name: 'b', not: false, values: [{ min: undefined, max: '1200px' }] },
+ ])
})
diff --git a/tests/opacity.test.js b/tests/opacity.test.js
index 82b437ce8..4b2b0eadf 100644
--- a/tests/opacity.test.js
+++ b/tests/opacity.test.js
@@ -1,1115 +1,1047 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(({ stable, oxide }) => {
- test('opacity', () => {
- let config = {
- darkMode: 'class',
- content: [
- {
- raw: html`
-
-
-
-
-
- `,
- },
- ],
- corePlugins: {
- backgroundOpacity: false,
- borderOpacity: false,
- divideOpacity: false,
- placeholderOpacity: false,
- textOpacity: false,
+test('opacity', () => {
+ let config = {
+ darkMode: 'class',
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+ `,
},
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .divide-black > :not([hidden]) ~ :not([hidden]),
- .border-black {
- border-color: #000;
- }
- .bg-black {
- background-color: #000;
- }
- .text-black,
- .placeholder-black::placeholder {
- color: #000;
- }
- `)
- })
- })
-
- test('colors defined as functions work when opacity plugins are disabled', () => {
- let config = {
- darkMode: 'class',
- content: [
- {
- raw: html`
-
-
-
-
-
- `,
- },
- ],
- theme: {
- colors: {
- primary: ({ opacityValue }) =>
- opacityValue === undefined
- ? 'rgb(var(--color-primary))'
- : `rgb(var(--color-primary) / ${opacityValue})`,
- },
- },
- corePlugins: {
- backgroundOpacity: false,
- borderOpacity: false,
- divideOpacity: false,
- placeholderOpacity: false,
- textOpacity: false,
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .divide-primary > :not([hidden]) ~ :not([hidden]),
- .border-primary {
- border-color: rgb(var(--color-primary));
- }
- .bg-primary {
- background-color: rgb(var(--color-primary));
- }
- .text-primary,
- .placeholder-primary::placeholder {
- color: rgb(var(--color-primary));
- }
- `)
- })
- })
-
- it('can use defining custom properties for colors (opacity plugins enabled)', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- theme: {
- colors: {
- primary: 'rgb(var(--color-primary) / )',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .divide-primary > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-opacity: 1;
- border-color: rgb(var(--color-primary) / var(--tw-divide-opacity));
- }
- .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-opacity: 0.5;
- }
- .border-primary {
- --tw-border-opacity: 1;
- border-color: rgb(var(--color-primary) / var(--tw-border-opacity));
- }
- .border-opacity-50 {
- --tw-border-opacity: 0.5;
- }
- .bg-primary {
- --tw-bg-opacity: 1;
- background-color: rgb(var(--color-primary) / var(--tw-bg-opacity));
- }
- .bg-opacity-50 {
- --tw-bg-opacity: 0.5;
- }
- .text-primary {
- --tw-text-opacity: 1;
- color: rgb(var(--color-primary) / var(--tw-text-opacity));
- }
- .text-opacity-50 {
- --tw-text-opacity: 0.5;
- }
- .placeholder-primary::placeholder {
- --tw-placeholder-opacity: 1;
- color: rgb(var(--color-primary) / var(--tw-placeholder-opacity));
- }
- .placeholder-opacity-50::placeholder {
- --tw-placeholder-opacity: 0.5;
- }
- .ring-primary {
- --tw-ring-opacity: 1;
- --tw-ring-color: rgb(var(--color-primary) / var(--tw-ring-opacity));
- }
- .ring-opacity-50 {
- --tw-ring-opacity: 0.5;
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .divide-primary > :not([hidden]) ~ :not([hidden]),
- .border-primary {
- border-color: rgb(var(--color-primary) / 1);
- }
- .bg-primary {
- background-color: rgb(var(--color-primary) / 1);
- }
- .text-primary,
- .placeholder-primary::placeholder {
- color: rgb(var(--color-primary) / 1);
- }
- .ring-primary {
- --tw-ring-color: rgb(var(--color-primary) / 1);
- }
- `)
- })
- })
-
- it('can use rgb helper when defining custom properties for colors (opacity plugins disabled)', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- theme: {
- colors: {
- primary: 'rgb(var(--color-primary) / )',
- },
- },
- corePlugins: {
- backgroundOpacity: false,
- borderOpacity: false,
- divideOpacity: false,
- placeholderOpacity: false,
- textOpacity: false,
- ringOpacity: false,
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .divide-primary > :not([hidden]) ~ :not([hidden]) {
- border-color: rgb(var(--color-primary) / 1);
- }
- .divide-primary\/50 > :not([hidden]) ~ :not([hidden]) {
- border-color: rgb(var(--color-primary) / 0.5);
- }
- .border-primary {
- border-color: rgb(var(--color-primary) / 1);
- }
- .border-primary\/50 {
- border-color: rgb(var(--color-primary) / 0.5);
- }
- .bg-primary {
- background-color: rgb(var(--color-primary) / 1);
- }
- .bg-primary\/50 {
- background-color: rgb(var(--color-primary) / 0.5);
- }
- .text-primary {
- color: rgb(var(--color-primary) / 1);
- }
- .text-primary\/50 {
- color: rgb(var(--color-primary) / 0.5);
- }
- .placeholder-primary::placeholder {
- color: rgb(var(--color-primary) / 1);
- }
- .placeholder-primary\/50::placeholder {
- color: rgb(var(--color-primary) / 0.5);
- }
- .ring-primary {
- --tw-ring-color: rgb(var(--color-primary) / 1);
- }
- .ring-primary\/50 {
- --tw-ring-color: rgb(var(--color-primary) / 0.5);
- }
- `)
- })
- })
-
- it('can use hsl helper when defining custom properties for colors (opacity plugins enabled)', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- theme: {
- colors: {
- primary: 'hsl(var(--color-primary) / )',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .divide-primary > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-opacity: 1;
- border-color: hsl(var(--color-primary) / var(--tw-divide-opacity));
- }
- .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-opacity: 0.5;
- }
- .border-primary {
- --tw-border-opacity: 1;
- border-color: hsl(var(--color-primary) / var(--tw-border-opacity));
- }
- .border-opacity-50 {
- --tw-border-opacity: 0.5;
- }
- .bg-primary {
- --tw-bg-opacity: 1;
- background-color: hsl(var(--color-primary) / var(--tw-bg-opacity));
- }
- .bg-opacity-50 {
- --tw-bg-opacity: 0.5;
- }
- .text-primary {
- --tw-text-opacity: 1;
- color: hsl(var(--color-primary) / var(--tw-text-opacity));
- }
- .text-opacity-50 {
- --tw-text-opacity: 0.5;
- }
- .placeholder-primary::placeholder {
- --tw-placeholder-opacity: 1;
- color: hsl(var(--color-primary) / var(--tw-placeholder-opacity));
- }
- .placeholder-opacity-50::placeholder {
- --tw-placeholder-opacity: 0.5;
- }
- .ring-primary {
- --tw-ring-opacity: 1;
- --tw-ring-color: hsl(var(--color-primary) / var(--tw-ring-opacity));
- }
- .ring-opacity-50 {
- --tw-ring-opacity: 0.5;
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .divide-primary > :not([hidden]) ~ :not([hidden]),
- .border-primary {
- border-color: hsl(var(--color-primary) / 1);
- }
- .bg-primary {
- background-color: hsl(var(--color-primary) / 1);
- }
- .text-primary,
- .placeholder-primary::placeholder {
- color: hsl(var(--color-primary) / 1);
- }
- .ring-primary {
- --tw-ring-color: hsl(var(--color-primary) / 1);
- }
- `)
- })
- })
-
- it('can use hsl helper when defining custom properties for colors (opacity plugins disabled)', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- theme: {
- colors: {
- primary: 'hsl(var(--color-primary) / )',
- },
- },
- corePlugins: {
- backgroundOpacity: false,
- borderOpacity: false,
- divideOpacity: false,
- placeholderOpacity: false,
- textOpacity: false,
- ringOpacity: false,
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .divide-primary > :not([hidden]) ~ :not([hidden]) {
- border-color: hsl(var(--color-primary) / 1);
- }
- .divide-primary\/50 > :not([hidden]) ~ :not([hidden]) {
- border-color: hsl(var(--color-primary) / 0.5);
- }
- .border-primary {
- border-color: hsl(var(--color-primary) / 1);
- }
- .border-primary\/50 {
- border-color: hsl(var(--color-primary) / 0.5);
- }
- .bg-primary {
- background-color: hsl(var(--color-primary) / 1);
- }
- .bg-primary\/50 {
- background-color: hsl(var(--color-primary) / 0.5);
- }
- .text-primary {
- color: hsl(var(--color-primary) / 1);
- }
- .text-primary\/50 {
- color: hsl(var(--color-primary) / 0.5);
- }
- .placeholder-primary::placeholder {
- color: hsl(var(--color-primary) / 1);
- }
- .placeholder-primary\/50::placeholder {
- color: hsl(var(--color-primary) / 0.5);
- }
- .ring-primary {
- --tw-ring-color: hsl(var(--color-primary) / 1);
- }
- .ring-primary\/50 {
- --tw-ring-color: hsl(var(--color-primary) / 0.5);
- }
- `)
- })
- })
-
- test('Theme function in JS can apply alpha values to colors (1)', () => {
- let input = css`
- @tailwind utilities;
- `
-
- let output = css`
- .text-foo {
- color: #3b82f680;
- }
- `
-
- return run(input, {
- content: [{ raw: html`text-foo` }],
- corePlugins: { textOpacity: false },
- theme: {
- colors: { blue: { 500: '#3b82f6' } },
- extend: {
- textColor: ({ theme }) => ({
- foo: theme('colors.blue.500 / 50%'),
- }),
- },
- },
- }).then((result) => {
- expect(result.css).toMatchFormattedCss(output)
- expect(result.warnings().length).toBe(0)
- })
- })
-
- test('Theme function in JS can apply alpha values to colors (2)', () => {
- let input = css`
- @tailwind utilities;
- `
-
- let output = css`
- .text-foo {
- color: #3b82f680;
- }
- `
-
- return run(input, {
- content: [{ raw: html`text-foo` }],
- corePlugins: { textOpacity: false },
- theme: {
- colors: { blue: { 500: '#3b82f6' } },
- extend: {
- textColor: ({ theme }) => ({
- foo: theme('colors.blue.500 / 0.5'),
- }),
- },
- },
- }).then((result) => {
- expect(result.css).toMatchFormattedCss(output)
- expect(result.warnings().length).toBe(0)
- })
- })
-
- test('Theme function in JS can apply alpha values to colors (3)', () => {
- let input = css`
- @tailwind utilities;
- `
-
- let output = css`
- .text-foo {
- color: rgb(59 130 246 / var(--my-alpha));
- }
- `
-
- return run(input, {
- content: [{ raw: html`text-foo` }],
- corePlugins: { textOpacity: false },
- theme: {
- colors: { blue: { 500: '#3b82f6' } },
- extend: {
- textColor: ({ theme }) => ({
- foo: theme('colors.blue.500 / var(--my-alpha)'),
- }),
- },
- },
- }).then((result) => {
- expect(result.css).toMatchFormattedCss(output)
- expect(result.warnings().length).toBe(0)
- })
- })
-
- test('Theme function in JS can apply alpha values to colors (4)', () => {
- let input = css`
- @tailwind utilities;
- `
-
- let output = css`
- .text-foo {
- color: #3c83f680;
- }
- `
-
- return run(input, {
- content: [{ raw: html`text-foo` }],
- corePlugins: { textOpacity: false },
- theme: {
- colors: { blue: { 500: 'hsl(217, 91%, 60%)' } },
- extend: {
- textColor: ({ theme }) => ({
- foo: theme('colors.blue.500 / 50%'),
- }),
- },
- },
- }).then((result) => {
- expect(result.css).toMatchFormattedCss(output)
- expect(result.warnings().length).toBe(0)
- })
- })
-
- test('Theme function in JS can apply alpha values to colors (5)', () => {
- let input = css`
- @tailwind utilities;
- `
-
- let output = css`
- .text-foo {
- color: #3c83f680;
- }
- `
-
- return run(input, {
- content: [{ raw: html`text-foo` }],
- corePlugins: { textOpacity: false },
- theme: {
- colors: { blue: { 500: 'hsl(217, 91%, 60%)' } },
- extend: {
- textColor: ({ theme }) => ({
- foo: theme('colors.blue.500 / 0.5'),
- }),
- },
- },
- }).then((result) => {
- expect(result.css).toMatchFormattedCss(output)
- expect(result.warnings().length).toBe(0)
- })
- })
-
- test('Theme function in JS can apply alpha values to colors (6)', () => {
- let input = css`
- @tailwind utilities;
- `
-
- let output = css`
- .text-foo {
- color: hsl(217 91% 60% / var(--my-alpha));
- }
- `
-
- return run(input, {
- content: [{ raw: html`text-foo` }],
- corePlugins: { textOpacity: false },
- theme: {
- colors: { blue: { 500: 'hsl(217, 91%, 60%)' } },
- extend: {
- textColor: ({ theme }) => ({
- foo: theme('colors.blue.500 / var(--my-alpha)'),
- }),
- },
- },
- }).then((result) => {
- expect(result.css).toMatchFormattedCss(output)
- expect(result.warnings().length).toBe(0)
- })
- })
-
- test('Theme function in JS can apply alpha values to colors (7)', () => {
- let input = css`
- @tailwind utilities;
- `
-
- let output = css`
- .text-foo {
- color: rgb(var(--foo) / var(--my-alpha));
- }
- `
-
- return run(input, {
- content: [{ raw: html`text-foo` }],
- corePlugins: { textOpacity: false },
- theme: {
- colors: {
- blue: {
- 500: 'rgb(var(--foo) / )',
- },
- },
- extend: {
- textColor: ({ theme }) => ({
- foo: theme('colors.blue.500 / var(--my-alpha)'),
- }),
- },
- },
- }).then((result) => {
- expect(result.css).toMatchFormattedCss(output)
- expect(result.warnings().length).toBe(0)
- })
- })
-
- test('Theme function prefers existing values in config', () => {
- let input = css`
- @tailwind utilities;
- `
-
- let output = css`
- .text-foo {
- color: purple;
- }
- `
-
- return run(input, {
- content: [{ raw: html`text-foo` }],
- corePlugins: { textOpacity: false },
- theme: {
- colors: {
- blue: {
- '500 / 50%': 'purple',
- },
- },
- extend: {
- textColor: ({ theme }) => ({
- foo: theme('colors.blue.500 / 50%'),
- }),
- },
- },
- }).then((result) => {
- expect(result.css).toMatchFormattedCss(output)
- expect(result.warnings().length).toBe(0)
- })
- })
-
- it('should be possible to use an as part of the color definition', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: ['backgroundColor', 'backgroundOpacity'],
- theme: {
- colors: {
- primary: 'rgb(var(--color-primary) / )',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .bg-primary {
- --tw-bg-opacity: 1;
- background-color: rgb(var(--color-primary) / var(--tw-bg-opacity));
- }
- `)
- })
- })
-
- it('should be possible to use an as part of the color definition with an opacity modifiers', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: ['backgroundColor', 'backgroundOpacity'],
- theme: {
- colors: {
- primary: 'rgb(var(--color-primary) / )',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .bg-primary\/50 {
- background-color: rgb(var(--color-primary) / 0.5);
- }
- `)
- })
- })
-
- it('should be possible to use an as part of the color definition with an opacity modifiers', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: ['backgroundColor'],
- theme: {
- colors: {
- primary: 'rgb(var(--color-primary) / )',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .bg-primary {
- background-color: rgb(var(--color-primary) / 1);
- }
- `)
- })
- })
-
- it('should be possible to use inside arbitrary values', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: ['backgroundColor', 'backgroundOpacity'],
- theme: {
- colors: {
- primary: 'rgb(var(--color-primary) / )',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .bg-\[rgb\(var\(--color-primary\)\/\\)\]\/50 {
- background-color: rgb(var(--color-primary) / 0.5);
- }
- `)
- })
- })
-
- it('Theme functions can reference values with slashes in brackets', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- theme: {
- colors: {
- 'a/b': '#000000',
- },
- extend: {
- backgroundColor: ({ theme }) => ({
- foo1: theme('colors[a/b]'),
- foo2: theme('colors[a/b]/50%'),
- }),
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .bg-foo1 {
- --tw-bg-opacity: 1;
- background-color: rgb(0 0 0 / var(--tw-bg-opacity));
- }
- .bg-foo2 {
- background-color: #00000080;
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .bg-foo1 {
- background-color: #000;
- }
- .bg-foo2 {
- background-color: #00000080;
- }
- `)
- })
- })
-
- it('works with opacity values defined as a placeholder or a function in when colors is a function', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
- },
- ],
- theme: {
- colors: () => ({
- foobar1: ({ opacityValue }) => `rgb(255 100 0 / ${opacityValue ?? '100%'})`,
- foobar2: `rgb(255 100 0 / )`,
- foobar3: {
- 100: ({ opacityValue }) => `rgb(255 100 0 / ${opacityValue ?? '100%'})`,
- 200: `rgb(255 100 0 / )`,
- },
- }),
- extend: {
- backgroundColor: ({ theme }) => ({
- foo10: theme('colors.foobar1'),
- foo20: theme('colors.foobar2'),
- foo30: theme('colors.foobar3.100'),
- foo40: theme('colors.foobar3.200'),
- foo11: theme('colors.foobar1 / 50%'),
- foo21: theme('colors.foobar2 / 50%'),
- foo31: theme('colors.foobar3.100 / 50%'),
- foo41: theme('colors.foobar3.200 / 50%'),
- }),
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .bg-foo10 {
- background-color: #ff6400;
- }
- .bg-foo11 {
- background-color: #ff640080;
- }
- .bg-foo20 {
- --tw-bg-opacity: 1;
- background-color: rgb(255 100 0 / var(--tw-bg-opacity));
- }
- .bg-foo21 {
- background-color: #ff640080;
- }
- .bg-foo30 {
- background-color: #ff6400;
- }
- .bg-foo31 {
- background-color: #ff640080;
- }
- .bg-foo40 {
- --tw-bg-opacity: 1;
- background-color: rgb(255 100 0 / var(--tw-bg-opacity));
- }
- .bg-foo41 {
- background-color: #ff640080;
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .bg-foo10 {
- background-color: #ff6400;
- }
- .bg-foo11 {
- background-color: #ff640080;
- }
- .bg-foo20 {
- background-color: #ff6400;
- }
- .bg-foo21 {
- background-color: #ff640080;
- }
- .bg-foo30 {
- background-color: #ff6400;
- }
- .bg-foo31 {
- background-color: #ff640080;
- }
- .bg-foo40 {
- background-color: #ff6400;
- }
- .bg-foo41 {
- background-color: #ff640080;
- }
- `)
- })
- })
-
- it('The disableColorOpacityUtilitiesByDefault flag disables the color opacity plugins and removes their variables', () => {
- let config = {
- future: {
- disableColorOpacityUtilitiesByDefault: true,
- },
- content: [
- {
- raw: html`
-
-
-
-
- `,
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .divide-blue-300 > :not([hidden]) ~ :not([hidden]) {
- border-color: #93c5fd;
- }
- .divide-blue-300\/50 > :not([hidden]) ~ :not([hidden]) {
- border-color: #93c5fd80;
- }
- .divide-blue-300\/\[var\(--my-opacity\)\] > :not([hidden]) ~ :not([hidden]) {
- border-color: rgb(147 197 253 / var(--my-opacity));
- }
- .border-blue-300 {
- border-color: #93c5fd;
- }
- .border-blue-300\/50 {
- border-color: #93c5fd80;
- }
- .border-blue-300\/\[var\(--my-opacity\)\] {
- border-color: rgb(147 197 253 / var(--my-opacity));
- }
- .bg-blue-300 {
- background-color: #93c5fd;
- }
- .bg-blue-300\/50 {
- background-color: #93c5fd80;
- }
- .bg-blue-300\/\[var\(--my-opacity\)\] {
- background-color: rgb(147 197 253 / var(--my-opacity));
- }
- .text-blue-300 {
- color: #93c5fd;
- }
- .text-blue-300\/50 {
- color: #93c5fd80;
- }
- .text-blue-300\/\[var\(--my-opacity\)\] {
- color: rgb(147 197 253 / var(--my-opacity));
- }
- .placeholder-blue-300::placeholder {
- color: #93c5fd;
- }
- .placeholder-blue-300\/50::placeholder {
- color: #93c5fd80;
- }
- .placeholder-blue-300\/\[var\(--my-opacity\)\]::placeholder {
- color: rgb(147 197 253 / var(--my-opacity));
- }
- .ring-blue-300 {
- --tw-ring-color: #93c5fd;
- }
- .ring-blue-300\/50 {
- --tw-ring-color: #93c5fd80;
- }
- .ring-blue-300\/\[var\(--my-opacity\)\] {
- --tw-ring-color: rgb(147 197 253 / var(--my-opacity));
- }
- `)
- })
- })
-
- it('You can re-enable any opacity plugin even when disableColorOpacityUtilitiesByDefault is enabled', () => {
- let config = {
- future: {
- disableColorOpacityUtilitiesByDefault: true,
- },
- corePlugins: {
- backgroundOpacity: true,
- borderOpacity: true,
- divideOpacity: true,
- placeholderOpacity: true,
- ringOpacity: true,
- textOpacity: true,
- },
- content: [
- {
- raw: html`
-
-
-
-
- `,
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .divide-blue-300 > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-opacity: 1;
- border-color: rgb(147 197 253 / var(--tw-divide-opacity));
- }
- .divide-blue-300\/50 > :not([hidden]) ~ :not([hidden]) {
- border-color: #93c5fd80;
- }
- .divide-blue-300\/\[var\(--my-opacity\)\] > :not([hidden]) ~ :not([hidden]) {
- border-color: rgb(147 197 253 / var(--my-opacity));
- }
- .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-opacity: 0.5;
- }
- .border-blue-300 {
- --tw-border-opacity: 1;
- border-color: rgb(147 197 253 / var(--tw-border-opacity));
- }
- .border-blue-300\/50 {
- border-color: #93c5fd80;
- }
- .border-blue-300\/\[var\(--my-opacity\)\] {
- border-color: rgb(147 197 253 / var(--my-opacity));
- }
- .border-opacity-50 {
- --tw-border-opacity: 0.5;
- }
- .bg-blue-300 {
- --tw-bg-opacity: 1;
- background-color: rgb(147 197 253 / var(--tw-bg-opacity));
- }
- .bg-blue-300\/50 {
- background-color: #93c5fd80;
- }
- .bg-blue-300\/\[var\(--my-opacity\)\] {
- background-color: rgb(147 197 253 / var(--my-opacity));
- }
- .bg-opacity-50 {
- --tw-bg-opacity: 0.5;
- }
- .text-blue-300 {
- --tw-text-opacity: 1;
- color: rgb(147 197 253 / var(--tw-text-opacity));
- }
- .text-blue-300\/50 {
- color: #93c5fd80;
- }
- .text-blue-300\/\[var\(--my-opacity\)\] {
- color: rgb(147 197 253 / var(--my-opacity));
- }
- .text-opacity-50 {
- --tw-text-opacity: 0.5;
- }
- .placeholder-blue-300::placeholder {
- --tw-placeholder-opacity: 1;
- color: rgb(147 197 253 / var(--tw-placeholder-opacity));
- }
- .placeholder-blue-300\/50::placeholder {
- color: #93c5fd80;
- }
- .placeholder-blue-300\/\[var\(--my-opacity\)\]::placeholder {
- color: rgb(147 197 253 / var(--my-opacity));
- }
- .placeholder-opacity-50::placeholder {
- --tw-placeholder-opacity: 0.5;
- }
- .ring-blue-300 {
- --tw-ring-opacity: 1;
- --tw-ring-color: rgb(147 197 253 / var(--tw-ring-opacity));
- }
- .ring-blue-300\/50 {
- --tw-ring-color: #93c5fd80;
- }
- .ring-blue-300\/\[var\(--my-opacity\)\] {
- --tw-ring-color: rgb(147 197 253 / var(--my-opacity));
- }
- .ring-opacity-50 {
- --tw-ring-opacity: 0.5;
- }
- `)
- })
- })
-
- it('can replace the potential alpha value in rgba/hsla syntax', async () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- colors: {
- 'primary-rgba': 'rgba(var(--color), 0.1)',
- 'primary-hsla': 'hsla(var(--color), 0.1)',
- },
- },
- }
-
- let result = await run('@tailwind utilities', config)
+ ],
+ corePlugins: {
+ backgroundOpacity: false,
+ borderOpacity: false,
+ divideOpacity: false,
+ placeholderOpacity: false,
+ textOpacity: false,
+ },
+ }
+ return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
- .text-primary-hsla\/50 {
- color: hsla(var(--color), 0.5);
+ .divide-black > :not([hidden]) ~ :not([hidden]),
+ .border-black {
+ border-color: #000;
}
- .text-primary-rgba\/50 {
- color: rgba(var(--color), 0.5);
+ .bg-black {
+ background-color: #000;
+ }
+ .text-black,
+ .placeholder-black::placeholder {
+ color: #000;
}
`)
})
})
+
+test('colors defined as functions work when opacity plugins are disabled', () => {
+ let config = {
+ darkMode: 'class',
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+ `,
+ },
+ ],
+ theme: {
+ colors: {
+ primary: ({ opacityValue }) =>
+ opacityValue === undefined
+ ? 'rgb(var(--color-primary))'
+ : `rgb(var(--color-primary) / ${opacityValue})`,
+ },
+ },
+ corePlugins: {
+ backgroundOpacity: false,
+ borderOpacity: false,
+ divideOpacity: false,
+ placeholderOpacity: false,
+ textOpacity: false,
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .divide-primary > :not([hidden]) ~ :not([hidden]),
+ .border-primary {
+ border-color: rgb(var(--color-primary));
+ }
+ .bg-primary {
+ background-color: rgb(var(--color-primary));
+ }
+ .text-primary,
+ .placeholder-primary::placeholder {
+ color: rgb(var(--color-primary));
+ }
+ `)
+ })
+})
+
+it('can use defining custom properties for colors (opacity plugins enabled)', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ theme: {
+ colors: {
+ primary: 'rgb(var(--color-primary) / )',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .divide-primary > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-opacity: 1;
+ border-color: rgb(var(--color-primary) / var(--tw-divide-opacity));
+ }
+ .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-opacity: 0.5;
+ }
+ .border-primary {
+ --tw-border-opacity: 1;
+ border-color: rgb(var(--color-primary) / var(--tw-border-opacity));
+ }
+ .border-opacity-50 {
+ --tw-border-opacity: 0.5;
+ }
+ .bg-primary {
+ --tw-bg-opacity: 1;
+ background-color: rgb(var(--color-primary) / var(--tw-bg-opacity));
+ }
+ .bg-opacity-50 {
+ --tw-bg-opacity: 0.5;
+ }
+ .text-primary {
+ --tw-text-opacity: 1;
+ color: rgb(var(--color-primary) / var(--tw-text-opacity));
+ }
+ .text-opacity-50 {
+ --tw-text-opacity: 0.5;
+ }
+ .placeholder-primary::placeholder {
+ --tw-placeholder-opacity: 1;
+ color: rgb(var(--color-primary) / var(--tw-placeholder-opacity));
+ }
+ .placeholder-opacity-50::placeholder {
+ --tw-placeholder-opacity: 0.5;
+ }
+ .ring-primary {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgb(var(--color-primary) / var(--tw-ring-opacity));
+ }
+ .ring-opacity-50 {
+ --tw-ring-opacity: 0.5;
+ }
+ `)
+ })
+})
+
+it('can use rgb helper when defining custom properties for colors (opacity plugins disabled)', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ theme: {
+ colors: {
+ primary: 'rgb(var(--color-primary) / )',
+ },
+ },
+ corePlugins: {
+ backgroundOpacity: false,
+ borderOpacity: false,
+ divideOpacity: false,
+ placeholderOpacity: false,
+ textOpacity: false,
+ ringOpacity: false,
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .divide-primary > :not([hidden]) ~ :not([hidden]) {
+ border-color: rgb(var(--color-primary) / 1);
+ }
+ .divide-primary\/50 > :not([hidden]) ~ :not([hidden]) {
+ border-color: rgb(var(--color-primary) / 0.5);
+ }
+ .border-primary {
+ border-color: rgb(var(--color-primary) / 1);
+ }
+ .border-primary\/50 {
+ border-color: rgb(var(--color-primary) / 0.5);
+ }
+ .bg-primary {
+ background-color: rgb(var(--color-primary) / 1);
+ }
+ .bg-primary\/50 {
+ background-color: rgb(var(--color-primary) / 0.5);
+ }
+ .text-primary {
+ color: rgb(var(--color-primary) / 1);
+ }
+ .text-primary\/50 {
+ color: rgb(var(--color-primary) / 0.5);
+ }
+ .placeholder-primary::placeholder {
+ color: rgb(var(--color-primary) / 1);
+ }
+ .placeholder-primary\/50::placeholder {
+ color: rgb(var(--color-primary) / 0.5);
+ }
+ .ring-primary {
+ --tw-ring-color: rgb(var(--color-primary) / 1);
+ }
+ .ring-primary\/50 {
+ --tw-ring-color: rgb(var(--color-primary) / 0.5);
+ }
+ `)
+ })
+})
+
+it('can use hsl helper when defining custom properties for colors (opacity plugins enabled)', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ theme: {
+ colors: {
+ primary: 'hsl(var(--color-primary) / )',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .divide-primary > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-opacity: 1;
+ border-color: hsl(var(--color-primary) / var(--tw-divide-opacity));
+ }
+ .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-opacity: 0.5;
+ }
+ .border-primary {
+ --tw-border-opacity: 1;
+ border-color: hsl(var(--color-primary) / var(--tw-border-opacity));
+ }
+ .border-opacity-50 {
+ --tw-border-opacity: 0.5;
+ }
+ .bg-primary {
+ --tw-bg-opacity: 1;
+ background-color: hsl(var(--color-primary) / var(--tw-bg-opacity));
+ }
+ .bg-opacity-50 {
+ --tw-bg-opacity: 0.5;
+ }
+ .text-primary {
+ --tw-text-opacity: 1;
+ color: hsl(var(--color-primary) / var(--tw-text-opacity));
+ }
+ .text-opacity-50 {
+ --tw-text-opacity: 0.5;
+ }
+ .placeholder-primary::placeholder {
+ --tw-placeholder-opacity: 1;
+ color: hsl(var(--color-primary) / var(--tw-placeholder-opacity));
+ }
+ .placeholder-opacity-50::placeholder {
+ --tw-placeholder-opacity: 0.5;
+ }
+ .ring-primary {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: hsl(var(--color-primary) / var(--tw-ring-opacity));
+ }
+ .ring-opacity-50 {
+ --tw-ring-opacity: 0.5;
+ }
+ `)
+ })
+})
+
+it('can use hsl helper when defining custom properties for colors (opacity plugins disabled)', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ theme: {
+ colors: {
+ primary: 'hsl(var(--color-primary) / )',
+ },
+ },
+ corePlugins: {
+ backgroundOpacity: false,
+ borderOpacity: false,
+ divideOpacity: false,
+ placeholderOpacity: false,
+ textOpacity: false,
+ ringOpacity: false,
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .divide-primary > :not([hidden]) ~ :not([hidden]) {
+ border-color: hsl(var(--color-primary) / 1);
+ }
+ .divide-primary\/50 > :not([hidden]) ~ :not([hidden]) {
+ border-color: hsl(var(--color-primary) / 0.5);
+ }
+ .border-primary {
+ border-color: hsl(var(--color-primary) / 1);
+ }
+ .border-primary\/50 {
+ border-color: hsl(var(--color-primary) / 0.5);
+ }
+ .bg-primary {
+ background-color: hsl(var(--color-primary) / 1);
+ }
+ .bg-primary\/50 {
+ background-color: hsl(var(--color-primary) / 0.5);
+ }
+ .text-primary {
+ color: hsl(var(--color-primary) / 1);
+ }
+ .text-primary\/50 {
+ color: hsl(var(--color-primary) / 0.5);
+ }
+ .placeholder-primary::placeholder {
+ color: hsl(var(--color-primary) / 1);
+ }
+ .placeholder-primary\/50::placeholder {
+ color: hsl(var(--color-primary) / 0.5);
+ }
+ .ring-primary {
+ --tw-ring-color: hsl(var(--color-primary) / 1);
+ }
+ .ring-primary\/50 {
+ --tw-ring-color: hsl(var(--color-primary) / 0.5);
+ }
+ `)
+ })
+})
+
+test('Theme function in JS can apply alpha values to colors (1)', () => {
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let output = css`
+ .text-foo {
+ color: #3b82f680;
+ }
+ `
+
+ return run(input, {
+ content: [{ raw: html`text-foo` }],
+ corePlugins: { textOpacity: false },
+ theme: {
+ colors: { blue: { 500: '#3b82f6' } },
+ extend: {
+ textColor: ({ theme }) => ({
+ foo: theme('colors.blue.500 / 50%'),
+ }),
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+})
+
+test('Theme function in JS can apply alpha values to colors (2)', () => {
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let output = css`
+ .text-foo {
+ color: #3b82f680;
+ }
+ `
+
+ return run(input, {
+ content: [{ raw: html`text-foo` }],
+ corePlugins: { textOpacity: false },
+ theme: {
+ colors: { blue: { 500: '#3b82f6' } },
+ extend: {
+ textColor: ({ theme }) => ({
+ foo: theme('colors.blue.500 / 0.5'),
+ }),
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+})
+
+test('Theme function in JS can apply alpha values to colors (3)', () => {
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let output = css`
+ .text-foo {
+ color: rgb(59 130 246 / var(--my-alpha));
+ }
+ `
+
+ return run(input, {
+ content: [{ raw: html`text-foo` }],
+ corePlugins: { textOpacity: false },
+ theme: {
+ colors: { blue: { 500: '#3b82f6' } },
+ extend: {
+ textColor: ({ theme }) => ({
+ foo: theme('colors.blue.500 / var(--my-alpha)'),
+ }),
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+})
+
+test('Theme function in JS can apply alpha values to colors (4)', () => {
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let output = css`
+ .text-foo {
+ color: #3c83f680;
+ }
+ `
+
+ return run(input, {
+ content: [{ raw: html`text-foo` }],
+ corePlugins: { textOpacity: false },
+ theme: {
+ colors: { blue: { 500: 'hsl(217, 91%, 60%)' } },
+ extend: {
+ textColor: ({ theme }) => ({
+ foo: theme('colors.blue.500 / 50%'),
+ }),
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+})
+
+test('Theme function in JS can apply alpha values to colors (5)', () => {
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let output = css`
+ .text-foo {
+ color: #3c83f680;
+ }
+ `
+
+ return run(input, {
+ content: [{ raw: html`text-foo` }],
+ corePlugins: { textOpacity: false },
+ theme: {
+ colors: { blue: { 500: 'hsl(217, 91%, 60%)' } },
+ extend: {
+ textColor: ({ theme }) => ({
+ foo: theme('colors.blue.500 / 0.5'),
+ }),
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+})
+
+test('Theme function in JS can apply alpha values to colors (6)', () => {
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let output = css`
+ .text-foo {
+ color: hsl(217 91% 60% / var(--my-alpha));
+ }
+ `
+
+ return run(input, {
+ content: [{ raw: html`text-foo` }],
+ corePlugins: { textOpacity: false },
+ theme: {
+ colors: { blue: { 500: 'hsl(217, 91%, 60%)' } },
+ extend: {
+ textColor: ({ theme }) => ({
+ foo: theme('colors.blue.500 / var(--my-alpha)'),
+ }),
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+})
+
+test('Theme function in JS can apply alpha values to colors (7)', () => {
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let output = css`
+ .text-foo {
+ color: rgb(var(--foo) / var(--my-alpha));
+ }
+ `
+
+ return run(input, {
+ content: [{ raw: html`text-foo` }],
+ corePlugins: { textOpacity: false },
+ theme: {
+ colors: {
+ blue: {
+ 500: 'rgb(var(--foo) / )',
+ },
+ },
+ extend: {
+ textColor: ({ theme }) => ({
+ foo: theme('colors.blue.500 / var(--my-alpha)'),
+ }),
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+})
+
+test('Theme function prefers existing values in config', () => {
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let output = css`
+ .text-foo {
+ color: purple;
+ }
+ `
+
+ return run(input, {
+ content: [{ raw: html`text-foo` }],
+ corePlugins: { textOpacity: false },
+ theme: {
+ colors: {
+ blue: {
+ '500 / 50%': 'purple',
+ },
+ },
+ extend: {
+ textColor: ({ theme }) => ({
+ foo: theme('colors.blue.500 / 50%'),
+ }),
+ },
+ },
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(output)
+ expect(result.warnings().length).toBe(0)
+ })
+})
+
+it('should be possible to use an as part of the color definition', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: ['backgroundColor', 'backgroundOpacity'],
+ theme: {
+ colors: {
+ primary: 'rgb(var(--color-primary) / )',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-primary {
+ --tw-bg-opacity: 1;
+ background-color: rgb(var(--color-primary) / var(--tw-bg-opacity));
+ }
+ `)
+ })
+})
+
+it('should be possible to use an as part of the color definition with an opacity modifiers', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: ['backgroundColor', 'backgroundOpacity'],
+ theme: {
+ colors: {
+ primary: 'rgb(var(--color-primary) / )',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-primary\/50 {
+ background-color: rgb(var(--color-primary) / 0.5);
+ }
+ `)
+ })
+})
+
+it('should be possible to use an as part of the color definition with an opacity modifiers', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: ['backgroundColor'],
+ theme: {
+ colors: {
+ primary: 'rgb(var(--color-primary) / )',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-primary {
+ background-color: rgb(var(--color-primary) / 1);
+ }
+ `)
+ })
+})
+
+it('should be possible to use inside arbitrary values', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: ['backgroundColor', 'backgroundOpacity'],
+ theme: {
+ colors: {
+ primary: 'rgb(var(--color-primary) / )',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-\[rgb\(var\(--color-primary\)\/\\)\]\/50 {
+ background-color: rgb(var(--color-primary) / 0.5);
+ }
+ `)
+ })
+})
+
+it('Theme functions can reference values with slashes in brackets', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ theme: {
+ colors: {
+ 'a/b': '#000000',
+ },
+ extend: {
+ backgroundColor: ({ theme }) => ({
+ foo1: theme('colors[a/b]'),
+ foo2: theme('colors[a/b]/50%'),
+ }),
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-foo1 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 0 0 / var(--tw-bg-opacity));
+ }
+ .bg-foo2 {
+ background-color: #00000080;
+ }
+ `)
+ })
+})
+
+it('works with opacity values defined as a placeholder or a function in when colors is a function', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ theme: {
+ colors: () => ({
+ foobar1: ({ opacityValue }) => `rgb(255 100 0 / ${opacityValue ?? '100%'})`,
+ foobar2: `rgb(255 100 0 / )`,
+ foobar3: {
+ 100: ({ opacityValue }) => `rgb(255 100 0 / ${opacityValue ?? '100%'})`,
+ 200: `rgb(255 100 0 / )`,
+ },
+ }),
+ extend: {
+ backgroundColor: ({ theme }) => ({
+ foo10: theme('colors.foobar1'),
+ foo20: theme('colors.foobar2'),
+ foo30: theme('colors.foobar3.100'),
+ foo40: theme('colors.foobar3.200'),
+ foo11: theme('colors.foobar1 / 50%'),
+ foo21: theme('colors.foobar2 / 50%'),
+ foo31: theme('colors.foobar3.100 / 50%'),
+ foo41: theme('colors.foobar3.200 / 50%'),
+ }),
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-foo10 {
+ background-color: #ff6400;
+ }
+ .bg-foo11 {
+ background-color: #ff640080;
+ }
+ .bg-foo20 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 100 0 / var(--tw-bg-opacity));
+ }
+ .bg-foo21 {
+ background-color: #ff640080;
+ }
+ .bg-foo30 {
+ background-color: #ff6400;
+ }
+ .bg-foo31 {
+ background-color: #ff640080;
+ }
+ .bg-foo40 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 100 0 / var(--tw-bg-opacity));
+ }
+ .bg-foo41 {
+ background-color: #ff640080;
+ }
+ `)
+ })
+})
+
+it('The disableColorOpacityUtilitiesByDefault flag disables the color opacity plugins and removes their variables', () => {
+ let config = {
+ future: {
+ disableColorOpacityUtilitiesByDefault: true,
+ },
+ content: [
+ {
+ raw: html`
+
+
+
+
+ `,
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .divide-blue-300 > :not([hidden]) ~ :not([hidden]) {
+ border-color: #93c5fd;
+ }
+ .divide-blue-300\/50 > :not([hidden]) ~ :not([hidden]) {
+ border-color: #93c5fd80;
+ }
+ .divide-blue-300\/\[var\(--my-opacity\)\] > :not([hidden]) ~ :not([hidden]) {
+ border-color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .border-blue-300 {
+ border-color: #93c5fd;
+ }
+ .border-blue-300\/50 {
+ border-color: #93c5fd80;
+ }
+ .border-blue-300\/\[var\(--my-opacity\)\] {
+ border-color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .bg-blue-300 {
+ background-color: #93c5fd;
+ }
+ .bg-blue-300\/50 {
+ background-color: #93c5fd80;
+ }
+ .bg-blue-300\/\[var\(--my-opacity\)\] {
+ background-color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .text-blue-300 {
+ color: #93c5fd;
+ }
+ .text-blue-300\/50 {
+ color: #93c5fd80;
+ }
+ .text-blue-300\/\[var\(--my-opacity\)\] {
+ color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .placeholder-blue-300::placeholder {
+ color: #93c5fd;
+ }
+ .placeholder-blue-300\/50::placeholder {
+ color: #93c5fd80;
+ }
+ .placeholder-blue-300\/\[var\(--my-opacity\)\]::placeholder {
+ color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .ring-blue-300 {
+ --tw-ring-color: #93c5fd;
+ }
+ .ring-blue-300\/50 {
+ --tw-ring-color: #93c5fd80;
+ }
+ .ring-blue-300\/\[var\(--my-opacity\)\] {
+ --tw-ring-color: rgb(147 197 253 / var(--my-opacity));
+ }
+ `)
+ })
+})
+
+it('You can re-enable any opacity plugin even when disableColorOpacityUtilitiesByDefault is enabled', () => {
+ let config = {
+ future: {
+ disableColorOpacityUtilitiesByDefault: true,
+ },
+ corePlugins: {
+ backgroundOpacity: true,
+ borderOpacity: true,
+ divideOpacity: true,
+ placeholderOpacity: true,
+ ringOpacity: true,
+ textOpacity: true,
+ },
+ content: [
+ {
+ raw: html`
+
+
+
+
+ `,
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .divide-blue-300 > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-opacity: 1;
+ border-color: rgb(147 197 253 / var(--tw-divide-opacity));
+ }
+ .divide-blue-300\/50 > :not([hidden]) ~ :not([hidden]) {
+ border-color: #93c5fd80;
+ }
+ .divide-blue-300\/\[var\(--my-opacity\)\] > :not([hidden]) ~ :not([hidden]) {
+ border-color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .divide-opacity-50 > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-opacity: 0.5;
+ }
+ .border-blue-300 {
+ --tw-border-opacity: 1;
+ border-color: rgb(147 197 253 / var(--tw-border-opacity));
+ }
+ .border-blue-300\/50 {
+ border-color: #93c5fd80;
+ }
+ .border-blue-300\/\[var\(--my-opacity\)\] {
+ border-color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .border-opacity-50 {
+ --tw-border-opacity: 0.5;
+ }
+ .bg-blue-300 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(147 197 253 / var(--tw-bg-opacity));
+ }
+ .bg-blue-300\/50 {
+ background-color: #93c5fd80;
+ }
+ .bg-blue-300\/\[var\(--my-opacity\)\] {
+ background-color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .bg-opacity-50 {
+ --tw-bg-opacity: 0.5;
+ }
+ .text-blue-300 {
+ --tw-text-opacity: 1;
+ color: rgb(147 197 253 / var(--tw-text-opacity));
+ }
+ .text-blue-300\/50 {
+ color: #93c5fd80;
+ }
+ .text-blue-300\/\[var\(--my-opacity\)\] {
+ color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .text-opacity-50 {
+ --tw-text-opacity: 0.5;
+ }
+ .placeholder-blue-300::placeholder {
+ --tw-placeholder-opacity: 1;
+ color: rgb(147 197 253 / var(--tw-placeholder-opacity));
+ }
+ .placeholder-blue-300\/50::placeholder {
+ color: #93c5fd80;
+ }
+ .placeholder-blue-300\/\[var\(--my-opacity\)\]::placeholder {
+ color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .placeholder-opacity-50::placeholder {
+ --tw-placeholder-opacity: 0.5;
+ }
+ .ring-blue-300 {
+ --tw-ring-opacity: 1;
+ --tw-ring-color: rgb(147 197 253 / var(--tw-ring-opacity));
+ }
+ .ring-blue-300\/50 {
+ --tw-ring-color: #93c5fd80;
+ }
+ .ring-blue-300\/\[var\(--my-opacity\)\] {
+ --tw-ring-color: rgb(147 197 253 / var(--my-opacity));
+ }
+ .ring-opacity-50 {
+ --tw-ring-opacity: 0.5;
+ }
+ `)
+ })
+})
+
+it('can replace the potential alpha value in rgba/hsla syntax', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ colors: {
+ 'primary-rgba': 'rgba(var(--color), 0.1)',
+ 'primary-hsla': 'hsla(var(--color), 0.1)',
+ },
+ },
+ }
+
+ let result = await run('@tailwind utilities', config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .text-primary-hsla\/50 {
+ color: hsla(var(--color), 0.5);
+ }
+ .text-primary-rgba\/50 {
+ color: rgba(var(--color), 0.5);
+ }
+ `)
+})
diff --git a/tests/oxide.test.js b/tests/oxide.test.js
deleted file mode 100644
index d2a697626..000000000
--- a/tests/oxide.test.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { crosscheck, run, html, css, defaults } from './util/run'
-
-crosscheck(({ stable, oxide }) => {
- stable.test.todo('space-x uses physical properties')
- oxide.test('space-x uses logical properties', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- return run('@tailwind base; @tailwind utilities;', config).then((result) => {
- expect(result.css).toMatchCss(css`
- ${defaults}
- .space-x-4 > :not([hidden]) ~ :not([hidden]) {
- --tw-space-x-reverse: 0;
- margin-inline-start: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
- margin-inline-end: calc(1rem * var(--tw-space-x-reverse));
- }
- `)
- })
- })
-})
diff --git a/tests/parallel-variants.test.js b/tests/parallel-variants.test.js
index a4d4dfc8f..61fb2cb33 100644
--- a/tests/parallel-variants.test.js
+++ b/tests/parallel-variants.test.js
@@ -1,138 +1,136 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(() => {
- test('basic parallel variants', async () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function test({ addVariant }) {
- addVariant('test', ['& *::test', '&::test'])
- },
- ],
- }
+test('basic parallel variants', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function test({ addVariant }) {
+ addVariant('test', ['& *::test', '&::test'])
+ },
+ ],
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .font-normal {
- font-weight: 400;
- }
- .test\:font-bold ::test {
- font-weight: 700;
- }
- .test\:font-medium ::test {
- font-weight: 500;
- }
- .hover\:test\:font-black ::test:hover {
- font-weight: 900;
- }
- .test\:font-bold::test {
- font-weight: 700;
- }
- .test\:font-medium::test {
- font-weight: 500;
- }
- .hover\:test\:font-black::test:hover {
- font-weight: 900;
- }
- `)
- })
- })
-
- test('parallel variants can be generated using a function that returns parallel variants', async () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function test({ addVariant }) {
- addVariant('test', () => ['& *::test', '&::test'])
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .font-normal {
- font-weight: 400;
- }
- .test\:font-bold ::test {
- font-weight: 700;
- }
- .test\:font-medium ::test {
- font-weight: 500;
- }
- .test\:font-bold::test {
- font-weight: 700;
- }
- .test\:font-medium::test {
- font-weight: 500;
- }
- .hover\:test\:font-black ::test:hover {
- font-weight: 900;
- }
- .hover\:test\:font-black::test:hover {
- font-weight: 900;
- }
- `)
- })
- })
-
- test('a function that returns parallel variants can modify the container', async () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function test({ addVariant }) {
- addVariant('test', ({ container }) => {
- container.walkDecls((decl) => {
- decl.value = `calc(0 + ${decl.value})`
- })
-
- return ['& *::test', '&::test']
- })
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .font-normal {
- font-weight: 400;
- }
- .test\:font-bold ::test {
- font-weight: 700;
- }
- .test\:font-medium ::test {
- font-weight: 500;
- }
- .test\:font-bold::test {
- font-weight: 700;
- }
- .test\:font-medium::test {
- font-weight: 500;
- }
- .hover\:test\:font-black ::test:hover {
- font-weight: 900;
- }
- .hover\:test\:font-black::test:hover {
- font-weight: 900;
- }
- `)
- })
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .font-normal {
+ font-weight: 400;
+ }
+ .test\:font-bold ::test {
+ font-weight: 700;
+ }
+ .test\:font-medium ::test {
+ font-weight: 500;
+ }
+ .hover\:test\:font-black ::test:hover {
+ font-weight: 900;
+ }
+ .test\:font-bold::test {
+ font-weight: 700;
+ }
+ .test\:font-medium::test {
+ font-weight: 500;
+ }
+ .hover\:test\:font-black::test:hover {
+ font-weight: 900;
+ }
+ `)
+ })
+})
+
+test('parallel variants can be generated using a function that returns parallel variants', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function test({ addVariant }) {
+ addVariant('test', () => ['& *::test', '&::test'])
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .font-normal {
+ font-weight: 400;
+ }
+ .test\:font-bold ::test {
+ font-weight: 700;
+ }
+ .test\:font-medium ::test {
+ font-weight: 500;
+ }
+ .test\:font-bold::test {
+ font-weight: 700;
+ }
+ .test\:font-medium::test {
+ font-weight: 500;
+ }
+ .hover\:test\:font-black ::test:hover {
+ font-weight: 900;
+ }
+ .hover\:test\:font-black::test:hover {
+ font-weight: 900;
+ }
+ `)
+ })
+})
+
+test('a function that returns parallel variants can modify the container', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function test({ addVariant }) {
+ addVariant('test', ({ container }) => {
+ container.walkDecls((decl) => {
+ decl.value = `calc(0 + ${decl.value})`
+ })
+
+ return ['& *::test', '&::test']
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .font-normal {
+ font-weight: 400;
+ }
+ .test\:font-bold ::test {
+ font-weight: 700;
+ }
+ .test\:font-medium ::test {
+ font-weight: 500;
+ }
+ .test\:font-bold::test {
+ font-weight: 700;
+ }
+ .test\:font-medium::test {
+ font-weight: 500;
+ }
+ .hover\:test\:font-black ::test:hover {
+ font-weight: 900;
+ }
+ .hover\:test\:font-black::test:hover {
+ font-weight: 900;
+ }
+ `)
})
})
diff --git a/tests/parse-candidate-strings.test.js b/tests/parse-candidate-strings.test.js
new file mode 100644
index 000000000..a97d66846
--- /dev/null
+++ b/tests/parse-candidate-strings.test.js
@@ -0,0 +1,441 @@
+import { parseCandidateStrings, IO, Parsing } from '@tailwindcss/oxide'
+import { defaultExtractor as createDefaultExtractor } from '../src/lib/defaultExtractor'
+
+let html = String.raw
+
+let defaultExtractor = createDefaultExtractor({ tailwindConfig: { separator: ':' } })
+
+function regexParser(str) {
+ return defaultExtractor(str)
+}
+
+function oxideParser(str) {
+ return parseCandidateStrings(
+ [{ content: str, extension: 'html' }],
+ IO.Sequential | Parsing.Parallel
+ )
+}
+
+function templateTable(classes) {
+ classes = classes.concat(
+ // Variants
+ classes.flatMap((c) => {
+ return [
+ // Simple variant
+ `hover:${c}`,
+
+ // Combined variant
+ `focus:hover:${c}`,
+
+ // Variant with dashes
+ `group-hover:group-focus:${c}`,
+
+ // With special characters
+ `:${c}`,
+
+ // With arbitrary values
+ `min-[300px]:${c}`,
+
+ // With arbitrary values
+ `[@media(hover:hover)]:${c}`,
+
+ // With parent selector
+ `[.foo_&]:${c}`,
+ ]
+ })
+ )
+
+ let classString = classes.join(' ')
+ let singleQuoteArraySyntaxWithSpace = `'${classes.join("', '")}'`
+ let singleQuoteArraySyntaxWithoutSpace = `'${classes.join("','")}'`
+
+ return [
+ ['Plain', classString],
+ ['HTML', html`
`],
+
+ ['JavaScript variable', `let foo = "${classString}"`],
+ ['JavaScript expression', `document.body.classList.add(['${classString}'].join(' '))`],
+ ['JavaScript object key', `let foo = {'${classString}': true}`],
+
+ ['JSX basic', html`
`],
+ ['JSX with JavaScript expression', html`
`],
+
+ ['Vue basic', html`
`],
+ [
+ 'Vue array (single quote, with space)',
+ html`
`,
+ ],
+ [
+ 'Vue array (single quote, without space)',
+ html`
`,
+ ],
+ ['Vue object (single quote)', html`
`],
+
+ ['Markdown code fences', ``],
+ ].map(([name, template]) => [name, template, classes])
+}
+
+describe.each([
+ ['Regex', regexParser],
+ ['Oxide', oxideParser],
+])('%s parser', (_, parse) => {
+ describe('basic utility classes', () => {
+ let classes = [
+ // One word classes
+ 'underline',
+
+ // With dashes
+ 'text-center',
+ 'pointer-events-none',
+
+ // With numbers
+ 'px-4',
+
+ // With special characters
+ 'px-1.5',
+ 'translate-x-1/2',
+ 'from-50%',
+
+ // With negative signs
+ '-translate-x-full',
+
+ // With halves and negative signs
+ '-translate-x-1/2',
+ ]
+
+ test.each(templateTable(classes))('%# — %s', (_, template, classes) => {
+ let extractions = parse(template)
+
+ for (let c of classes) {
+ expect(extractions).toContain(c)
+ }
+ })
+ })
+
+ describe('utility classes with arbitrary values', () => {
+ let classes = [
+ // With simple number
+ 'px-[0]',
+ 'px-[0.5]',
+
+ // With number and unit
+ 'px-[123px]',
+ 'px-[123.45px]',
+
+ // With special symbols
+ 'bg-[#bada55]',
+ // ^
+ 'bg-[color:#bada55]',
+ // ^^
+ 'content-[>]',
+ // ^
+ 'content-[<]',
+ // ^
+
+ // With functions and math expressions
+ 'px-[calc(100%-1rem)]',
+ 'px-[theme(spacing.1)]',
+ 'px-[theme(spacing[1.5])]',
+
+ // With spaces (replaced by `_`)
+ 'bg-[rgb(255_0_0)]',
+
+ // Examples with combinations
+ 'w-[calc(100%_-_theme("spacing[1.5]))"]',
+ 'fill-[oklab(59.69%_0.1007_0.1191_/_0.5)]/[33.7%]',
+ 'fill-[color:oklab(59.69%_0.1007_0.1191_/_0.5)]/[33.7%]',
+ 'shadow-[inset_0_-3em_3em_rgba(0,_0,_0,_0.1),_0_0_0_2px_rgb(255,_255,_255),_0.3em_0.3em_1em_rgba(0,_0,_0,_0.3)]',
+ ]
+
+ test.each(templateTable(classes))('%# — %s', (_, template, classes) => {
+ let extractions = parse(template)
+
+ for (let c of classes) {
+ // TODO: This is a bug in the RegEx parser.
+ if (!extractions.includes(c) && parse === regexParser) {
+ continue
+ }
+
+ expect(extractions).toContain(c)
+ }
+ })
+ })
+
+ describe('utility classes with modifiers', () => {
+ let classes = [
+ // With simple modifiers
+ 'bg-red-500/50',
+
+ // With arbitrary modifiers
+ 'bg-red-500/[0.5]',
+ 'bg-red-500/[50%]',
+ 'bg-red-500/[var(--opacity)]',
+
+ // With spces (replaced by `_`)
+ 'bg-red-500/[var(--opacity,_50%)]',
+ ]
+
+ test.each(templateTable(classes))('%# — %s', (_, template, classes) => {
+ let extractions = parse(template)
+
+ for (let c of classes) {
+ expect(extractions).toContain(c)
+ }
+ })
+ })
+
+ describe('utility classes with important modifier', () => {
+ let classes = ['!bg-red-500', '!bg-[#bada55]', '![display:flex]', '!-translate-x-1/2']
+
+ test.each(templateTable(classes))('%# — %s', (_, template, classes) => {
+ let extractions = parse(template)
+
+ for (let c of classes) {
+ // TODO: This is a bug in the RegEx parser.
+ if (!extractions.includes(c) && parse === regexParser) {
+ continue
+ }
+
+ expect(extractions).toContain(c)
+ }
+ })
+ })
+
+ describe('arbitrary properties', () => {
+ let classes = [
+ // With simple arbitrary property
+ '[display:flex]',
+ ]
+
+ test.each(templateTable(classes))('%# — %s', (_, template, classes) => {
+ let extractions = parse(template)
+
+ for (let c of classes) {
+ expect(extractions).toContain(c)
+ }
+ })
+ })
+
+ describe('arbitrary values with quotes', () => {
+ test('arbitrary values with single quotes', async () => {
+ let extractions = parse(`
+
+ `)
+
+ expect(extractions).toContain(`content-['hello_world']`)
+ expect(extractions).toContain(`hover:content-['hello_world_2']`)
+ expect(extractions).toContain(`hover:focus:content-['hello_world_3']`)
+ })
+
+ test('arbitrary values with double quotes', async () => {
+ let extractions = parse(`
+
+ `)
+
+ expect(extractions).toContain(`content-["hello_world"]`)
+ expect(extractions).toContain(`hover:content-["hello_world_2"]`)
+ expect(extractions).toContain(`hover:focus:content-["hello_world_3"]`)
+ })
+
+ test('arbitrary values with some single quoted values', async () => {
+ let extractions = parse(`
+
+ `)
+
+ expect(extractions).toContain(`font-['Open_Sans',_system-ui,_sans-serif]`)
+ expect(extractions).toContain(`hover:font-['Proxima_Nova',_system-ui,_sans-serif]`)
+ expect(extractions).toContain(`hover:focus:font-['Inter_var',_system-ui,_sans-serif]`)
+ })
+
+ test('arbitrary values with some double quoted values', async () => {
+ let extractions = parse(`
+
+ `)
+
+ expect(extractions).toContain(`font-["Open_Sans",_system-ui,_sans-serif]`)
+ expect(extractions).toContain(`hover:font-["Proxima_Nova",_system-ui,_sans-serif]`)
+ expect(extractions).toContain(`hover:focus:font-["Inter_var",_system-ui,_sans-serif]`)
+ })
+
+ test('arbitrary values with escaped underscores', async () => {
+ let extractions = parse(`
+
+ `)
+
+ expect(extractions).toContain(`content-['hello\\_world']`)
+ expect(extractions).toContain(`hover:content-['hello\\_world\\_2']`)
+ expect(extractions).toContain(`hover:focus:content-['hello\\_world\\_3']`)
+ })
+
+ test('arbitrary values with angle brackets in single quotes', async () => {
+ let extractions = parse(`
+
+ `)
+
+ expect(extractions).toContain(`content-['>']`)
+ expect(extractions).toContain(`hover:content-['<']`)
+ expect(extractions).toContain(`hover:focus:content-['>']`)
+ })
+
+ test('arbitrary values with angle brackets in double quotes', async () => {
+ let extractions = parse(`
+ "] hover:content-["<"] hover:focus:content-[">"]">
+ `)
+
+ expect(extractions).toContain(`content-[">"]`)
+ expect(extractions).toContain(`hover:content-["<"]`)
+ expect(extractions).toContain(`hover:focus:content-[">"]`)
+ })
+
+ test('arbitrary values with theme lookup using quotes', () => {
+ let extractions = parse(`
+
+ `)
+
+ expect(extractions).toContain(`[--y:theme('colors.blue.500')]`)
+ expect(extractions).toContain(`[color:var(--y)]`)
+ })
+ })
+
+ test('special characters', async () => {
+ let extractions = parse(`
+
+ `)
+
+ expect(extractions).toContain(`:font-bold`)
+ })
+
+ test('with single quotes array within template literal', async () => {
+ let extractions = parse(`
`)
+
+ expect(extractions).toContain('pr-1.5')
+ })
+
+ test('with double quotes array within template literal', async () => {
+ let extractions = parse(`
`)
+
+ expect(extractions).toContain('pr-1.5')
+ })
+
+ test('with single quotes array within function', async () => {
+ let extractions = parse(`document.body.classList.add(['pl-1.5'].join(" "));`)
+
+ expect(extractions).toContain('pl-1.5')
+ })
+
+ test('with double quotes array within function', async () => {
+ let extractions = parse(`document.body.classList.add(["pl-1.5"].join(" "));`)
+
+ expect(extractions).toContain('pl-1.5')
+ })
+
+ test('with angle brackets', async () => {
+ let extractions = parse(
+ `test
`
+ )
+
+ expect(extractions).toContain('bg-blue-200')
+ expect(extractions).toContain('shadow-xl')
+ expect(extractions).not.toContain('>shadow-xl')
+ expect(extractions).not.toContain('shadow-xl<')
+ })
+
+ test('markdown code fences', async () => {
+ let extractions = parse('')
+
+ expect(extractions).toContain('font-bold')
+ expect(extractions).toContain('font-normal')
+ expect(extractions).not.toContain('.font-bold')
+ expect(extractions).not.toContain('.font-normal')
+ })
+
+ test('classes in slim templates', async () => {
+ let extractions = parse(`
+ p.bg-red-500.text-sm
+ 'This is a paragraph
+ small.italic.text-gray-500
+ '(Look mom, no closing tag!)
+ `)
+
+ expect(extractions).toContain('bg-red-500')
+ expect(extractions).toContain('text-sm')
+ expect(extractions).toContain('italic')
+ expect(extractions).toContain('text-gray-500')
+ })
+
+ test('multi-word + arbitrary values + quotes', async () => {
+ let extractions = parse(`
+ grid-cols-['repeat(2)']
+ `)
+
+ expect(extractions).toContain(`grid-cols-['repeat(2)']`)
+ })
+
+ test('a lot of data', () => {
+ let extractions = parse('underline '.repeat(2 ** 17))
+
+ expect(extractions).toContain(`underline`)
+ })
+
+ test('ruby percent string array', () => {
+ let extractions = parse('%w[text-[#bada55]]')
+
+ expect(extractions).toContain(`text-[#bada55]`)
+ })
+
+ test('arbitrary properties followed by square bracketed stuff', () => {
+ let extractions = parse(
+ '[foo]
'
+ )
+
+ expect(extractions).toContain(`[display:inherit]`)
+ })
+
+ describe('Vue', () => {
+ test('Class object syntax', () => {
+ let extractions = parse(
+ `[foo] `
+ )
+
+ expect(extractions).toContain(`underline`, `font-bold`)
+ })
+
+ test('Class array syntax', () => {
+ // With leading space
+ let extractions = parse(
+ `
[foo]
`
+ )
+
+ expect(extractions).toContain(`underline`, `font-bold`, `flex`, `block`)
+
+ // Without leading space
+ extractions = parse(
+ `
[foo]
`
+ )
+
+ expect(extractions).toContain(`underline`, `font-bold`, `flex`, `block`)
+ })
+ })
+
+ describe('anti-test', () => {
+ it('should not parse candidates without spaces in object-like syntax', () => {
+ let extractions = parse(html`
`)
+
+ // The oxide parser _does_ allow this!
+ if (parse !== oxideParser) {
+ expect(extractions).not.toContain(`underline:isActive`)
+ expect(extractions).not.toContain(`flex:isOnline`)
+ expect(extractions).not.toContain(`md:bold`)
+ }
+
+ expect(extractions).not.toContain(`underline`)
+ expect(extractions).not.toContain(`isActive`)
+ expect(extractions).not.toContain(`flex`)
+ expect(extractions).not.toContain(`isOnline`)
+ expect(extractions).not.toContain(`md`)
+ expect(extractions).not.toContain(`bold`)
+ })
+ })
+})
diff --git a/tests/parseAnimationValue.test.js b/tests/parseAnimationValue.test.js
index 098178079..293d6fbec 100644
--- a/tests/parseAnimationValue.test.js
+++ b/tests/parseAnimationValue.test.js
@@ -1,203 +1,200 @@
import parseAnimationValue from '../src/util/parseAnimationValue'
-import { crosscheck } from './util/run'
-crosscheck(() => {
- describe('Tailwind Defaults', () => {
- it.each([
- [
- 'spin 1s linear infinite',
- {
- value: 'spin 1s linear infinite',
- name: 'spin',
- duration: '1s',
- timingFunction: 'linear',
- iterationCount: 'infinite',
- },
- ],
- [
- 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
- {
- value: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
- name: 'ping',
- duration: '1s',
- timingFunction: 'cubic-bezier(0, 0, 0.2, 1)',
- iterationCount: 'infinite',
- },
- ],
- [
- 'bounce 1s infinite',
- {
- value: 'bounce 1s infinite',
- name: 'bounce',
- duration: '1s',
- iterationCount: 'infinite',
- },
- ],
- ])('should be possible to parse: "%s"', (input, expected) => {
- const parsed = parseAnimationValue(input)
- expect(parsed).toHaveLength(1)
- expect(parsed[0]).toEqual(expected)
- })
- })
-
- describe('css variables', () => {
- it('should be possible to use css variables', () => {
- let parsed = parseAnimationValue('jump var(--animation-duration, 10s) linear infinite')
- expect(parsed[0]).toEqual({
- value: 'jump var(--animation-duration, 10s) linear infinite',
- name: 'jump',
+describe('Tailwind Defaults', () => {
+ it.each([
+ [
+ 'spin 1s linear infinite',
+ {
+ value: 'spin 1s linear infinite',
+ name: 'spin',
+ duration: '1s',
timingFunction: 'linear',
iterationCount: 'infinite',
- unknown: ['var(--animation-duration, 10s)'],
- })
- })
- })
-
- describe('MDN Examples', () => {
- it.each([
- [
- '3s ease-in 1s 2 reverse both paused slidein',
- {
- value: '3s ease-in 1s 2 reverse both paused slidein',
- delay: '1s',
- direction: 'reverse',
- duration: '3s',
- fillMode: 'both',
- iterationCount: '2',
- name: 'slidein',
- playState: 'paused',
- timingFunction: 'ease-in',
- },
- ],
- [
- 'slidein 3s linear 1s',
- {
- value: 'slidein 3s linear 1s',
- delay: '1s',
- duration: '3s',
- name: 'slidein',
- timingFunction: 'linear',
- },
- ],
- ['slidein 3s', { value: 'slidein 3s', duration: '3s', name: 'slidein' }],
- ])('should be possible to parse: "%s"', (input, expected) => {
- const parsed = parseAnimationValue(input)
- expect(parsed).toHaveLength(1)
- expect(parsed[0]).toEqual(expected)
- })
- })
-
- describe('duration & delay', () => {
- it.each([
- // Positive seconds (integer)
- ['spin 2s 1s linear', { duration: '2s', delay: '1s' }],
-
- // Negative seconds (integer)
- ['spin -2s -1s linear', { duration: '-2s', delay: '-1s' }],
-
- // Positive seconds (float)
- ['spin 2.321s 1.321s linear', { duration: '2.321s', delay: '1.321s' }],
-
- // Negative seconds (float)
- ['spin -2.321s -1.321s linear', { duration: '-2.321s', delay: '-1.321s' }],
-
- // Positive milliseconds (integer)
- ['spin 200ms 100ms linear', { duration: '200ms', delay: '100ms' }],
-
- // Negative milliseconds (integer)
- ['spin -200ms -100ms linear', { duration: '-200ms', delay: '-100ms' }],
-
- // Positive milliseconds (float)
- ['spin 200.321ms 100.321ms linear', { duration: '200.321ms', delay: '100.321ms' }],
-
- // Negative milliseconds (float)
- ['spin -200.321ms -100.321ms linear', { duration: '-200.321ms', delay: '-100.321ms' }],
- ])('should be possible to parse "%s" into %o', (input, { duration, delay }) => {
- const parsed = parseAnimationValue(input)
- expect(parsed).toHaveLength(1)
- expect(parsed[0].duration).toEqual(duration)
- expect(parsed[0].delay).toEqual(delay)
- })
- })
-
- describe('iteration count', () => {
- it.each([
- // Number
- ['1 spin 200s 100s linear', '1'],
- ['spin 2 200s 100s linear', '2'],
- ['spin 200s 3 100s linear', '3'],
- ['spin 200s 100s 4 linear', '4'],
- ['spin 200s 100s linear 5', '5'],
-
- // Infinite
- ['infinite spin 200s 100s linear', 'infinite'],
- ['spin infinite 200s 100s linear', 'infinite'],
- ['spin 200s infinite 100s linear', 'infinite'],
- ['spin 200s 100s infinite linear', 'infinite'],
- ['spin 200s 100s linear infinite', 'infinite'],
- ])(
- 'should be possible to parse "%s" with an iteraction count of "%s"',
- (input, iterationCount) => {
- const parsed = parseAnimationValue(input)
- expect(parsed).toHaveLength(1)
- expect(parsed[0].iterationCount).toEqual(iterationCount)
- }
- )
- })
-
- describe('multiple animations', () => {
- it('should be possible to parse multiple applications at once', () => {
- const input = [
- 'spin 1s linear infinite',
- 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
- 'pulse 2s cubic-bezier(0.4, 0, 0.6) infinite',
- ].join(',')
-
- const parsed = parseAnimationValue(input)
- expect(parsed).toHaveLength(3)
- expect(parsed).toEqual([
- {
- value: 'spin 1s linear infinite',
- name: 'spin',
- duration: '1s',
- timingFunction: 'linear',
- iterationCount: 'infinite',
- },
- {
- value: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
- name: 'ping',
- duration: '1s',
- timingFunction: 'cubic-bezier(0, 0, 0.2, 1)',
- iterationCount: 'infinite',
- },
- {
- value: 'pulse 2s cubic-bezier(0.4, 0, 0.6) infinite',
- name: 'pulse',
- duration: '2s',
- timingFunction: 'cubic-bezier(0.4, 0, 0.6)',
- iterationCount: 'infinite',
- },
- ])
- })
- })
-
- it.each`
- input | value | direction | playState | fillMode | iterationCount | timingFunction | duration | delay | name
- ${'1s spin 1s infinite'} | ${'1s spin 1s infinite'} | ${undefined} | ${undefined} | ${undefined} | ${'infinite'} | ${undefined} | ${'1s'} | ${'1s'} | ${'spin'}
- ${'infinite infinite 1s 1s'} | ${'infinite infinite 1s 1s'} | ${undefined} | ${undefined} | ${undefined} | ${'infinite'} | ${undefined} | ${'1s'} | ${'1s'} | ${'infinite'}
- ${'ease 1s ease 1s'} | ${'ease 1s ease 1s'} | ${undefined} | ${undefined} | ${undefined} | ${undefined} | ${'ease'} | ${'1s'} | ${'1s'} | ${'ease'}
- ${'normal paused backwards infinite ease-in 1s 2s name'} | ${'normal paused backwards infinite ease-in 1s 2s name'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
- ${'paused backwards infinite ease-in 1s 2s name normal'} | ${'paused backwards infinite ease-in 1s 2s name normal'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
- ${'backwards infinite ease-in 1s 2s name normal paused'} | ${'backwards infinite ease-in 1s 2s name normal paused'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
- ${'infinite ease-in 1s 2s name normal paused backwards'} | ${'infinite ease-in 1s 2s name normal paused backwards'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
- ${'ease-in 1s 2s name normal paused backwards infinite'} | ${'ease-in 1s 2s name normal paused backwards infinite'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
- ${'1s 2s name normal paused backwards infinite ease-in'} | ${'1s 2s name normal paused backwards infinite ease-in'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
- ${'2s name normal paused backwards infinite ease-in 1s'} | ${'2s name normal paused backwards infinite ease-in 1s'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'2s'} | ${'1s'} | ${'name'}
- ${'name normal paused backwards infinite ease-in 1s 2s'} | ${'name normal paused backwards infinite ease-in 1s 2s'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
- ${' name normal paused backwards infinite ease-in 1s 2s '} | ${'name normal paused backwards infinite ease-in 1s 2s'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
- `('should parse "$input" correctly', ({ input, ...expected }) => {
- let parsed = parseAnimationValue(input)
+ },
+ ],
+ [
+ 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
+ {
+ value: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
+ name: 'ping',
+ duration: '1s',
+ timingFunction: 'cubic-bezier(0, 0, 0.2, 1)',
+ iterationCount: 'infinite',
+ },
+ ],
+ [
+ 'bounce 1s infinite',
+ {
+ value: 'bounce 1s infinite',
+ name: 'bounce',
+ duration: '1s',
+ iterationCount: 'infinite',
+ },
+ ],
+ ])('should be possible to parse: "%s"', (input, expected) => {
+ const parsed = parseAnimationValue(input)
expect(parsed).toHaveLength(1)
expect(parsed[0]).toEqual(expected)
})
})
+
+describe('css variables', () => {
+ it('should be possible to use css variables', () => {
+ let parsed = parseAnimationValue('jump var(--animation-duration, 10s) linear infinite')
+ expect(parsed[0]).toEqual({
+ value: 'jump var(--animation-duration, 10s) linear infinite',
+ name: 'jump',
+ timingFunction: 'linear',
+ iterationCount: 'infinite',
+ unknown: ['var(--animation-duration, 10s)'],
+ })
+ })
+})
+
+describe('MDN Examples', () => {
+ it.each([
+ [
+ '3s ease-in 1s 2 reverse both paused slidein',
+ {
+ value: '3s ease-in 1s 2 reverse both paused slidein',
+ delay: '1s',
+ direction: 'reverse',
+ duration: '3s',
+ fillMode: 'both',
+ iterationCount: '2',
+ name: 'slidein',
+ playState: 'paused',
+ timingFunction: 'ease-in',
+ },
+ ],
+ [
+ 'slidein 3s linear 1s',
+ {
+ value: 'slidein 3s linear 1s',
+ delay: '1s',
+ duration: '3s',
+ name: 'slidein',
+ timingFunction: 'linear',
+ },
+ ],
+ ['slidein 3s', { value: 'slidein 3s', duration: '3s', name: 'slidein' }],
+ ])('should be possible to parse: "%s"', (input, expected) => {
+ const parsed = parseAnimationValue(input)
+ expect(parsed).toHaveLength(1)
+ expect(parsed[0]).toEqual(expected)
+ })
+})
+
+describe('duration & delay', () => {
+ it.each([
+ // Positive seconds (integer)
+ ['spin 2s 1s linear', { duration: '2s', delay: '1s' }],
+
+ // Negative seconds (integer)
+ ['spin -2s -1s linear', { duration: '-2s', delay: '-1s' }],
+
+ // Positive seconds (float)
+ ['spin 2.321s 1.321s linear', { duration: '2.321s', delay: '1.321s' }],
+
+ // Negative seconds (float)
+ ['spin -2.321s -1.321s linear', { duration: '-2.321s', delay: '-1.321s' }],
+
+ // Positive milliseconds (integer)
+ ['spin 200ms 100ms linear', { duration: '200ms', delay: '100ms' }],
+
+ // Negative milliseconds (integer)
+ ['spin -200ms -100ms linear', { duration: '-200ms', delay: '-100ms' }],
+
+ // Positive milliseconds (float)
+ ['spin 200.321ms 100.321ms linear', { duration: '200.321ms', delay: '100.321ms' }],
+
+ // Negative milliseconds (float)
+ ['spin -200.321ms -100.321ms linear', { duration: '-200.321ms', delay: '-100.321ms' }],
+ ])('should be possible to parse "%s" into %o', (input, { duration, delay }) => {
+ const parsed = parseAnimationValue(input)
+ expect(parsed).toHaveLength(1)
+ expect(parsed[0].duration).toEqual(duration)
+ expect(parsed[0].delay).toEqual(delay)
+ })
+})
+
+describe('iteration count', () => {
+ it.each([
+ // Number
+ ['1 spin 200s 100s linear', '1'],
+ ['spin 2 200s 100s linear', '2'],
+ ['spin 200s 3 100s linear', '3'],
+ ['spin 200s 100s 4 linear', '4'],
+ ['spin 200s 100s linear 5', '5'],
+
+ // Infinite
+ ['infinite spin 200s 100s linear', 'infinite'],
+ ['spin infinite 200s 100s linear', 'infinite'],
+ ['spin 200s infinite 100s linear', 'infinite'],
+ ['spin 200s 100s infinite linear', 'infinite'],
+ ['spin 200s 100s linear infinite', 'infinite'],
+ ])(
+ 'should be possible to parse "%s" with an iteraction count of "%s"',
+ (input, iterationCount) => {
+ const parsed = parseAnimationValue(input)
+ expect(parsed).toHaveLength(1)
+ expect(parsed[0].iterationCount).toEqual(iterationCount)
+ }
+ )
+})
+
+describe('multiple animations', () => {
+ it('should be possible to parse multiple applications at once', () => {
+ const input = [
+ 'spin 1s linear infinite',
+ 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
+ 'pulse 2s cubic-bezier(0.4, 0, 0.6) infinite',
+ ].join(',')
+
+ const parsed = parseAnimationValue(input)
+ expect(parsed).toHaveLength(3)
+ expect(parsed).toEqual([
+ {
+ value: 'spin 1s linear infinite',
+ name: 'spin',
+ duration: '1s',
+ timingFunction: 'linear',
+ iterationCount: 'infinite',
+ },
+ {
+ value: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
+ name: 'ping',
+ duration: '1s',
+ timingFunction: 'cubic-bezier(0, 0, 0.2, 1)',
+ iterationCount: 'infinite',
+ },
+ {
+ value: 'pulse 2s cubic-bezier(0.4, 0, 0.6) infinite',
+ name: 'pulse',
+ duration: '2s',
+ timingFunction: 'cubic-bezier(0.4, 0, 0.6)',
+ iterationCount: 'infinite',
+ },
+ ])
+ })
+})
+
+it.each`
+ input | value | direction | playState | fillMode | iterationCount | timingFunction | duration | delay | name
+ ${'1s spin 1s infinite'} | ${'1s spin 1s infinite'} | ${undefined} | ${undefined} | ${undefined} | ${'infinite'} | ${undefined} | ${'1s'} | ${'1s'} | ${'spin'}
+ ${'infinite infinite 1s 1s'} | ${'infinite infinite 1s 1s'} | ${undefined} | ${undefined} | ${undefined} | ${'infinite'} | ${undefined} | ${'1s'} | ${'1s'} | ${'infinite'}
+ ${'ease 1s ease 1s'} | ${'ease 1s ease 1s'} | ${undefined} | ${undefined} | ${undefined} | ${undefined} | ${'ease'} | ${'1s'} | ${'1s'} | ${'ease'}
+ ${'normal paused backwards infinite ease-in 1s 2s name'} | ${'normal paused backwards infinite ease-in 1s 2s name'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
+ ${'paused backwards infinite ease-in 1s 2s name normal'} | ${'paused backwards infinite ease-in 1s 2s name normal'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
+ ${'backwards infinite ease-in 1s 2s name normal paused'} | ${'backwards infinite ease-in 1s 2s name normal paused'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
+ ${'infinite ease-in 1s 2s name normal paused backwards'} | ${'infinite ease-in 1s 2s name normal paused backwards'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
+ ${'ease-in 1s 2s name normal paused backwards infinite'} | ${'ease-in 1s 2s name normal paused backwards infinite'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
+ ${'1s 2s name normal paused backwards infinite ease-in'} | ${'1s 2s name normal paused backwards infinite ease-in'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
+ ${'2s name normal paused backwards infinite ease-in 1s'} | ${'2s name normal paused backwards infinite ease-in 1s'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'2s'} | ${'1s'} | ${'name'}
+ ${'name normal paused backwards infinite ease-in 1s 2s'} | ${'name normal paused backwards infinite ease-in 1s 2s'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
+ ${' name normal paused backwards infinite ease-in 1s 2s '} | ${'name normal paused backwards infinite ease-in 1s 2s'} | ${'normal'} | ${'paused'} | ${'backwards'} | ${'infinite'} | ${'ease-in'} | ${'1s'} | ${'2s'} | ${'name'}
+`('should parse "$input" correctly', ({ input, ...expected }) => {
+ let parsed = parseAnimationValue(input)
+ expect(parsed).toHaveLength(1)
+ expect(parsed[0]).toEqual(expected)
+})
diff --git a/tests/parseObjectStyles.test.js b/tests/parseObjectStyles.test.js
index c6121977f..c1a602038 100644
--- a/tests/parseObjectStyles.test.js
+++ b/tests/parseObjectStyles.test.js
@@ -1,319 +1,317 @@
import parseObjectStyles from '../src/util/parseObjectStyles'
import postcss from 'postcss'
-import { crosscheck, css } from './util/run'
+import { css } from './util/run'
function toCss(nodes) {
return postcss.root({ nodes }).toString()
}
-crosscheck(() => {
- test('it parses simple single class definitions', () => {
- const result = parseObjectStyles({
- '.foobar': {
- backgroundColor: 'red',
- color: 'white',
- padding: '1rem',
- },
- })
-
- expect(toCss(result)).toMatchFormattedCss(css`
- .foobar {
- color: #fff;
- background-color: red;
- padding: 1rem;
- }
- `)
+test('it parses simple single class definitions', () => {
+ let result = parseObjectStyles({
+ '.foobar': {
+ backgroundColor: 'red',
+ color: 'white',
+ padding: '1rem',
+ },
})
- test('it parses multiple class definitions', () => {
- const result = parseObjectStyles({
- '.foo': {
- backgroundColor: 'red',
- color: 'white',
- padding: '1rem',
- },
- '.bar': {
- width: '200px',
- height: '100px',
- },
- })
+ expect(toCss(result)).toMatchFormattedCss(css`
+ .foobar {
+ background-color: red;
+ color: white;
+ padding: 1rem;
+ }
+ `)
+})
- expect(toCss(result)).toMatchFormattedCss(css`
- .foo {
- color: #fff;
- background-color: red;
- padding: 1rem;
- }
- .bar {
- width: 200px;
- height: 100px;
- }
- `)
+test('it parses multiple class definitions', () => {
+ let result = parseObjectStyles({
+ '.foo': {
+ backgroundColor: 'red',
+ color: 'white',
+ padding: '1rem',
+ },
+ '.bar': {
+ width: '200px',
+ height: '100px',
+ },
})
- test('it parses nested pseudo-selectors', () => {
- const result = parseObjectStyles({
- '.foo': {
- backgroundColor: 'red',
- color: 'white',
- padding: '1rem',
- '&:hover': {
- backgroundColor: 'orange',
- },
- '&:focus': {
- backgroundColor: 'blue',
- },
- },
- })
+ expect(toCss(result)).toMatchFormattedCss(css`
+ .foo {
+ background-color: red;
+ color: white;
+ padding: 1rem;
+ }
+ .bar {
+ width: 200px;
+ height: 100px;
+ }
+ `)
+})
- expect(toCss(result)).toMatchFormattedCss(css`
+test('it parses nested pseudo-selectors', () => {
+ let result = parseObjectStyles({
+ '.foo': {
+ backgroundColor: 'red',
+ color: 'white',
+ padding: '1rem',
+ '&:hover': {
+ backgroundColor: 'orange',
+ },
+ '&:focus': {
+ backgroundColor: 'blue',
+ },
+ },
+ })
+
+ expect(toCss(result)).toMatchFormattedCss(css`
+ .foo {
+ background-color: red;
+ color: white;
+ padding: 1rem;
+ }
+ .foo:hover {
+ background-color: orange;
+ }
+ .foo:focus {
+ background-color: blue;
+ }
+ `)
+})
+
+test('it parses top-level media queries', () => {
+ let result = parseObjectStyles({
+ '@media (min-width: 200px)': {
+ '.foo': {
+ backgroundColor: 'orange',
+ },
+ },
+ })
+
+ expect(toCss(result)).toMatchFormattedCss(css`
+ @media (min-width: 200px) {
.foo {
- color: #fff;
- background-color: red;
- padding: 1rem;
- }
- .foo:hover {
background-color: orange;
}
- .foo:focus {
- background-color: #00f;
- }
- `)
- })
+ }
+ `)
+})
- test('it parses top-level media queries', () => {
- const result = parseObjectStyles({
+test('it parses nested media queries', () => {
+ let result = parseObjectStyles({
+ '.foo': {
+ backgroundColor: 'red',
+ color: 'white',
+ padding: '1rem',
'@media (min-width: 200px)': {
- '.foo': {
- backgroundColor: 'orange',
- },
+ backgroundColor: 'orange',
},
- })
-
- expect(toCss(result)).toMatchFormattedCss(css`
- @media (min-width: 200px) {
- .foo {
- background-color: orange;
- }
- }
- `)
+ },
})
- test('it parses nested media queries', () => {
- const result = parseObjectStyles({
- '.foo': {
- backgroundColor: 'red',
- color: 'white',
- padding: '1rem',
+ expect(toCss(result)).toMatchFormattedCss(css`
+ .foo {
+ background-color: red;
+ color: white;
+ padding: 1rem;
+ }
+ @media (min-width: 200px) {
+ .foo {
+ background-color: orange;
+ }
+ }
+ `)
+})
+
+test('it bubbles nested screen rules', () => {
+ let result = parseObjectStyles({
+ '.foo': {
+ backgroundColor: 'red',
+ color: 'white',
+ padding: '1rem',
+ '@screen sm': {
+ backgroundColor: 'orange',
+ },
+ },
+ })
+
+ expect(toCss(result)).toMatchFormattedCss(css`
+ .foo {
+ background-color: red;
+ color: white;
+ padding: 1rem;
+ }
+ @screen sm {
+ .foo {
+ background-color: orange;
+ }
+ }
+ `)
+})
+
+test('it parses pseudo-selectors in nested media queries', () => {
+ let result = parseObjectStyles({
+ '.foo': {
+ backgroundColor: 'red',
+ color: 'white',
+ padding: '1rem',
+ '&:hover': {
'@media (min-width: 200px)': {
backgroundColor: 'orange',
},
},
- })
-
- expect(toCss(result)).toMatchFormattedCss(css`
- .foo {
- color: #fff;
- background-color: red;
- padding: 1rem;
- }
- @media (min-width: 200px) {
- .foo {
- background-color: orange;
- }
- }
- `)
+ },
})
- test('it bubbles nested screen rules', () => {
- const result = parseObjectStyles({
- '.foo': {
- backgroundColor: 'red',
- color: 'white',
- padding: '1rem',
- '@screen sm': {
- backgroundColor: 'orange',
- },
- },
- })
-
- expect(toCss(result)).toMatchFormattedCss(css`
- .foo {
- color: #fff;
- background-color: red;
- padding: 1rem;
- }
- @screen sm {
- .foo {
- background-color: orange;
- }
- }
- `)
- })
-
- test('it parses pseudo-selectors in nested media queries', () => {
- const result = parseObjectStyles({
- '.foo': {
- backgroundColor: 'red',
- color: 'white',
- padding: '1rem',
- '&:hover': {
- '@media (min-width: 200px)': {
- backgroundColor: 'orange',
- },
- },
- },
- })
-
- expect(toCss(result)).toMatchFormattedCss(css`
- .foo {
- color: #fff;
- background-color: red;
- padding: 1rem;
- }
- @media (min-width: 200px) {
- .foo:hover {
- background-color: orange;
- }
- }
- `)
- })
-
- test('it parses descendant selectors', () => {
- const result = parseObjectStyles({
- '.foo': {
- backgroundColor: 'red',
- color: 'white',
- padding: '1rem',
- '.bar': {
- backgroundColor: 'orange',
- },
- },
- })
-
- expect(toCss(result)).toMatchFormattedCss(css`
- .foo {
- color: #fff;
- background-color: red;
- padding: 1rem;
- }
- .foo .bar {
+ expect(toCss(result)).toMatchFormattedCss(css`
+ .foo {
+ background-color: red;
+ color: white;
+ padding: 1rem;
+ }
+ @media (min-width: 200px) {
+ .foo:hover {
background-color: orange;
}
- `)
+ }
+ `)
+})
+
+test('it parses descendant selectors', () => {
+ let result = parseObjectStyles({
+ '.foo': {
+ backgroundColor: 'red',
+ color: 'white',
+ padding: '1rem',
+ '.bar': {
+ backgroundColor: 'orange',
+ },
+ },
})
- test('it parses nested multi-class selectors', () => {
- const result = parseObjectStyles({
- '.foo': {
- backgroundColor: 'red',
- color: 'white',
- padding: '1rem',
+ expect(toCss(result)).toMatchFormattedCss(css`
+ .foo {
+ background-color: red;
+ color: white;
+ padding: 1rem;
+ }
+ .foo .bar {
+ background-color: orange;
+ }
+ `)
+})
+
+test('it parses nested multi-class selectors', () => {
+ let result = parseObjectStyles({
+ '.foo': {
+ backgroundColor: 'red',
+ color: 'white',
+ padding: '1rem',
+ '&.bar': {
+ backgroundColor: 'orange',
+ },
+ },
+ })
+
+ expect(toCss(result)).toMatchFormattedCss(css`
+ .foo {
+ background-color: red;
+ color: white;
+ padding: 1rem;
+ }
+ .foo.bar {
+ background-color: orange;
+ }
+ `)
+})
+
+test('it parses nested multi-class selectors in media queries', () => {
+ let result = parseObjectStyles({
+ '.foo': {
+ backgroundColor: 'red',
+ color: 'white',
+ padding: '1rem',
+ '@media (min-width: 200px)': {
'&.bar': {
backgroundColor: 'orange',
},
},
- })
+ },
+ })
- expect(toCss(result)).toMatchFormattedCss(css`
- .foo {
- color: #fff;
- background-color: red;
- padding: 1rem;
- }
+ expect(toCss(result)).toMatchFormattedCss(css`
+ .foo {
+ background-color: red;
+ color: white;
+ padding: 1rem;
+ }
+ @media (min-width: 200px) {
.foo.bar {
background-color: orange;
}
- `)
- })
-
- test('it parses nested multi-class selectors in media queries', () => {
- const result = parseObjectStyles({
- '.foo': {
- backgroundColor: 'red',
- color: 'white',
- padding: '1rem',
- '@media (min-width: 200px)': {
- '&.bar': {
- backgroundColor: 'orange',
- },
- },
- },
- })
-
- expect(toCss(result)).toMatchFormattedCss(css`
- .foo {
- color: #fff;
- background-color: red;
- padding: 1rem;
- }
- @media (min-width: 200px) {
- .foo.bar {
- background-color: orange;
- }
- }
- `)
- })
-
- test('it strips empty selectors when nesting', () => {
- const result = parseObjectStyles({
- '.foo': {
- '.bar': {
- backgroundColor: 'orange',
- },
- },
- })
-
- expect(toCss(result)).toMatchFormattedCss(css`
- .foo .bar {
- background-color: orange;
- }
- `)
- })
-
- test('it can parse an array of styles', () => {
- const result = parseObjectStyles([
- {
- '.foo': {
- backgroundColor: 'orange',
- },
- },
- {
- '.bar': {
- backgroundColor: 'red',
- },
- },
- {
- '.foo': {
- backgroundColor: 'blue',
- },
- },
- ])
-
- expect(toCss(result)).toMatchFormattedCss(css`
- .foo {
- background-color: orange;
- }
- .bar {
- background-color: red;
- }
- .foo {
- background-color: #00f;
- }
- `)
- })
-
- test('custom properties preserve their case', () => {
- const result = parseObjectStyles({
- ':root': {
- '--colors-aColor-500': '0',
- },
- })
-
- expect(toCss(result)).toMatchFormattedCss(css`
- :root {
- --colors-aColor-500: 0;
- }
- `)
- })
+ }
+ `)
+})
+
+test('it strips empty selectors when nesting', () => {
+ let result = parseObjectStyles({
+ '.foo': {
+ '.bar': {
+ backgroundColor: 'orange',
+ },
+ },
+ })
+
+ expect(toCss(result)).toMatchFormattedCss(css`
+ .foo .bar {
+ background-color: orange;
+ }
+ `)
+})
+
+test('it can parse an array of styles', () => {
+ let result = parseObjectStyles([
+ {
+ '.foo': {
+ backgroundColor: 'orange',
+ },
+ },
+ {
+ '.bar': {
+ backgroundColor: 'red',
+ },
+ },
+ {
+ '.foo': {
+ backgroundColor: 'blue',
+ },
+ },
+ ])
+
+ expect(toCss(result)).toMatchFormattedCss(css`
+ .foo {
+ background-color: orange;
+ }
+ .bar {
+ background-color: red;
+ }
+ .foo {
+ background-color: blue;
+ }
+ `)
+})
+
+test('custom properties preserve their case', () => {
+ let result = parseObjectStyles({
+ ':root': {
+ '--colors-aColor-500': '0',
+ },
+ })
+
+ expect(toCss(result)).toMatchFormattedCss(css`
+ :root {
+ --colors-aColor-500: 0;
+ }
+ `)
})
diff --git a/tests/plugins/divide.test.js b/tests/plugins/divide.test.js
index 314233bea..a5ffa826f 100644
--- a/tests/plugins/divide.test.js
+++ b/tests/plugins/divide.test.js
@@ -1,102 +1,91 @@
-import { crosscheck, run, html, css, defaults } from '../util/run'
+import { run, html, css, defaults } from '../util/run'
-crosscheck(({ stable, oxide }) => {
- it('should add the divide styles for divide-y and a default border color', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
+it('should add the divide styles for divide-y and a default border color', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
- return run('@tailwind base; @tailwind utilities;', config).then((result) => {
- expect(result.css).toMatchCss(css`
- ${defaults}
+ return run('@tailwind base; @tailwind utilities;', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults}
- .divide-y > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-y-reverse: 0;
- border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
- border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
- }
- `)
- })
- })
-
- it('should add the divide styles for divide-x and a default border color', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- return run('@tailwind base; @tailwind utilities;', config).then((result) => {
- stable.expect(result.css).toMatchCss(css`
- ${defaults}
- .divide-x > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-x-reverse: 0;
- border-right-width: calc(1px * var(--tw-divide-x-reverse));
- border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
- }
- `)
-
- oxide.expect(result.css).toMatchCss(css`
- ${defaults}
- .divide-x > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-x-reverse: 0;
- border-inline-end-width: calc(1px * var(--tw-divide-x-reverse));
- border-inline-start-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
- }
- `)
- })
- })
-
- it('should add the divide styles for divide-y-reverse and a default border color', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- return run('@tailwind base; @tailwind utilities;', config).then((result) => {
- expect(result.css).toMatchCss(css`
- ${defaults}
- .divide-y-reverse > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-y-reverse: 1;
- }
- `)
- })
- })
-
- it('should add the divide styles for divide-x-reverse and a default border color', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- return run('@tailwind base; @tailwind utilities;', config).then((result) => {
- expect(result.css).toMatchCss(css`
- ${defaults}
- .divide-x-reverse > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-x-reverse: 1;
- }
- `)
- })
- })
-
- it('should only inject the base styles once if we use divide and border at the same time', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- return run('@tailwind base; @tailwind utilities;', config).then((result) => {
- expect(result.css).toMatchCss(css`
- ${defaults}
- .divide-y > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-y-reverse: 0;
- border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
- border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
- }
- .border-r {
- border-right-width: 1px;
- }
- `)
- })
+ .divide-y > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-y-reverse: 0;
+ border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
+ border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
+ }
+ `)
+ })
+})
+
+it('should add the divide styles for divide-x and a default border color', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ return run('@tailwind base; @tailwind utilities;', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults}
+ .divide-x > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-x-reverse: 0;
+ border-right-width: calc(1px * var(--tw-divide-x-reverse));
+ border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse)));
+ }
+ `)
+ })
+})
+
+it('should add the divide styles for divide-y-reverse and a default border color', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ return run('@tailwind base; @tailwind utilities;', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults}
+ .divide-y-reverse > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-y-reverse: 1;
+ }
+ `)
+ })
+})
+
+it('should add the divide styles for divide-x-reverse and a default border color', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ return run('@tailwind base; @tailwind utilities;', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults}
+ .divide-x-reverse > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-x-reverse: 1;
+ }
+ `)
+ })
+})
+
+it('should only inject the base styles once if we use divide and border at the same time', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ return run('@tailwind base; @tailwind utilities;', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults}
+ .divide-y > :not([hidden]) ~ :not([hidden]) {
+ --tw-divide-y-reverse: 0;
+ border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
+ border-bottom-width: calc(1px * var(--tw-divide-y-reverse));
+ }
+ .border-r {
+ border-right-width: 1px;
+ }
+ `)
})
})
diff --git a/tests/plugins/fontFamily.test.js b/tests/plugins/fontFamily.test.js
index 1170dac8e..c0c7aaceb 100644
--- a/tests/plugins/fontFamily.test.js
+++ b/tests/plugins/fontFamily.test.js
@@ -1,101 +1,99 @@
-import { crosscheck, run, html, css } from '../util/run'
+import { run, html, css } from '../util/run'
-crosscheck(() => {
- test('font-family utilities can be defined as a string', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- fontFamily: {
- sans: 'Helvetica, Arial, sans-serif',
- },
+test('font-family utilities can be defined as a string', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ fontFamily: {
+ sans: 'Helvetica, Arial, sans-serif',
},
- }
+ },
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .font-sans {
- font-family: Helvetica, Arial, sans-serif;
- }
- `)
- })
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .font-sans {
+ font-family: Helvetica, Arial, sans-serif;
+ }
+ `)
})
+})
- test('font-family utilities can be defined as an array', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- fontFamily: {
- sans: ['Helvetica', 'Arial', 'sans-serif'],
- },
+test('font-family utilities can be defined as an array', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ fontFamily: {
+ sans: ['Helvetica', 'Arial', 'sans-serif'],
},
- }
+ },
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .font-sans {
- font-family: Helvetica, Arial, sans-serif;
- }
- `)
- })
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .font-sans {
+ font-family: Helvetica, Arial, sans-serif;
+ }
+ `)
})
+})
- test('font-family values are not automatically escaped', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- fontFamily: {
- sans: ["'Exo 2'", 'sans-serif'],
- },
+test('font-family values are not automatically escaped', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ fontFamily: {
+ sans: ["'Exo 2'", 'sans-serif'],
},
- }
+ },
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .font-sans {
- font-family: 'Exo 2', sans-serif;
- }
- `)
- })
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .font-sans {
+ font-family: 'Exo 2', sans-serif;
+ }
+ `)
})
+})
- test('font-feature-settings can be provided when families are defined as a string', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- fontFamily: {
- sans: ['Helvetica, Arial, sans-serif', { fontFeatureSettings: '"cv11", "ss01"' }],
- },
+test('font-feature-settings can be provided when families are defined as a string', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ fontFamily: {
+ sans: ['Helvetica, Arial, sans-serif', { fontFeatureSettings: '"cv11", "ss01"' }],
},
- }
+ },
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .font-sans {
- font-feature-settings: 'cv11', 'ss01';
- font-family: Helvetica, Arial, sans-serif;
- }
- `)
- })
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .font-sans {
+ font-feature-settings: 'cv11', 'ss01';
+ font-family: Helvetica, Arial, sans-serif;
+ }
+ `)
})
+})
- test('font-feature-settings can be provided when families are defined as an array', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- fontFamily: {
- sans: [['Helvetica', 'Arial', 'sans-serif'], { fontFeatureSettings: '"cv11", "ss01"' }],
- },
+test('font-feature-settings can be provided when families are defined as an array', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ fontFamily: {
+ sans: [['Helvetica', 'Arial', 'sans-serif'], { fontFeatureSettings: '"cv11", "ss01"' }],
},
- }
+ },
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .font-sans {
- font-feature-settings: 'cv11', 'ss01';
- font-family: Helvetica, Arial, sans-serif;
- }
- `)
- })
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .font-sans {
+ font-feature-settings: 'cv11', 'ss01';
+ font-family: Helvetica, Arial, sans-serif;
+ }
+ `)
})
})
@@ -110,10 +108,10 @@ test('font-variation-settings can be provided when families are defined as a str
}
return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(`
+ expect(result.css).toMatchFormattedCss(css`
.font-sans {
+ font-variation-settings: 'opsz' 32;
font-family: Inter, sans-serif;
- font-variation-settings: "opsz" 32;
}
`)
})
@@ -130,10 +128,10 @@ test('font-variation-settings can be provided when families are defined as an ar
}
return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(`
+ expect(result.css).toMatchFormattedCss(css`
.font-sans {
+ font-variation-settings: 'opsz' 32;
font-family: Inter, sans-serif;
- font-variation-settings: "opsz" 32;
}
`)
})
diff --git a/tests/plugins/fontSize.test.js b/tests/plugins/fontSize.test.js
index 08e88bb76..99f67eaf6 100644
--- a/tests/plugins/fontSize.test.js
+++ b/tests/plugins/fontSize.test.js
@@ -1,197 +1,195 @@
-import { crosscheck, run, html, css } from '../util/run'
+import { run, html, css } from '../util/run'
-crosscheck(() => {
- test('font-size utilities can include a default line-height', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- fontSize: {
- sm: '12px',
- md: ['16px', '24px'],
- lg: ['20px', '28px'],
- },
+test('font-size utilities can include a default line-height', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ fontSize: {
+ sm: '12px',
+ md: ['16px', '24px'],
+ lg: ['20px', '28px'],
},
- }
+ },
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .text-lg {
- font-size: 20px;
- line-height: 28px;
- }
- .text-md {
- font-size: 16px;
- line-height: 24px;
- }
- .text-sm {
- font-size: 12px;
- }
- `)
- })
- })
-
- test('font-size utilities can include a default letter-spacing', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- fontSize: {
- sm: '12px',
- md: ['16px', { letterSpacing: '-0.01em' }],
- lg: ['20px', { letterSpacing: '-0.02em' }],
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .text-lg {
- letter-spacing: -0.02em;
- font-size: 20px;
- }
- .text-md {
- letter-spacing: -0.01em;
- font-size: 16px;
- }
- .text-sm {
- font-size: 12px;
- }
- `)
- })
- })
-
- test('font-size utilities can include a default line-height and letter-spacing', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- fontSize: {
- sm: '12px',
- md: ['16px', { lineHeight: '24px', letterSpacing: '-0.01em' }],
- lg: ['20px', { lineHeight: '28px', letterSpacing: '-0.02em' }],
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .text-lg {
- letter-spacing: -0.02em;
- font-size: 20px;
- line-height: 28px;
- }
- .text-md {
- letter-spacing: -0.01em;
- font-size: 16px;
- line-height: 24px;
- }
- .text-sm {
- font-size: 12px;
- }
- `)
- })
- })
-
- test('font-size utilities can include a font-weight', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- fontSize: {
- sm: '12px',
- md: ['16px', { lineHeight: '24px', fontWeight: 500 }],
- lg: ['20px', { lineHeight: '28px', fontWeight: 'bold' }],
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .text-lg {
- font-size: 20px;
- font-weight: bold;
- line-height: 28px;
- }
- .text-md {
- font-size: 16px;
- font-weight: 500;
- line-height: 24px;
- }
- .text-sm {
- font-size: 12px;
- }
- `)
- })
- })
-
- test('font-size utilities can include a line-height modifier', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- theme: {
- fontSize: {
- sm: ['12px', '20px'],
- base: ['16px', '24px'],
- },
- lineHeight: {
- 6: '24px',
- 7: '28px',
- 8: '32px',
- },
- },
- }
-
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchCss(css`
- .text-\[13px\]\/6 {
- font-size: 13px;
- line-height: 24px;
- }
- .text-\[17px\]\/\[23px\] {
- font-size: 17px;
- line-height: 23px;
- }
- .text-sm {
- font-size: 12px;
- line-height: 20px;
- }
- .text-sm\/6 {
- font-size: 12px;
- line-height: 24px;
- }
- .text-sm\/\[21px\] {
- font-size: 12px;
- line-height: 21px;
- }
- @media (min-width: 768px) {
- .md\:text-\[19px\]\/8 {
- font-size: 19px;
- line-height: 32px;
- }
- .md\:text-\[21px\]\/\[29px\] {
- font-size: 21px;
- line-height: 29px;
- }
- .md\:text-base {
- font-size: 16px;
- line-height: 24px;
- }
- .md\:text-base\/7 {
- font-size: 16px;
- line-height: 28px;
- }
- .md\:text-base\/\[33px\] {
- font-size: 16px;
- line-height: 33px;
- }
- }
- `)
- })
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .text-lg {
+ font-size: 20px;
+ line-height: 28px;
+ }
+ .text-md {
+ font-size: 16px;
+ line-height: 24px;
+ }
+ .text-sm {
+ font-size: 12px;
+ }
+ `)
+ })
+})
+
+test('font-size utilities can include a default letter-spacing', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ fontSize: {
+ sm: '12px',
+ md: ['16px', { letterSpacing: '-0.01em' }],
+ lg: ['20px', { letterSpacing: '-0.02em' }],
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .text-lg {
+ letter-spacing: -0.02em;
+ font-size: 20px;
+ }
+ .text-md {
+ letter-spacing: -0.01em;
+ font-size: 16px;
+ }
+ .text-sm {
+ font-size: 12px;
+ }
+ `)
+ })
+})
+
+test('font-size utilities can include a default line-height and letter-spacing', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ fontSize: {
+ sm: '12px',
+ md: ['16px', { lineHeight: '24px', letterSpacing: '-0.01em' }],
+ lg: ['20px', { lineHeight: '28px', letterSpacing: '-0.02em' }],
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .text-lg {
+ letter-spacing: -0.02em;
+ font-size: 20px;
+ line-height: 28px;
+ }
+ .text-md {
+ letter-spacing: -0.01em;
+ font-size: 16px;
+ line-height: 24px;
+ }
+ .text-sm {
+ font-size: 12px;
+ }
+ `)
+ })
+})
+
+test('font-size utilities can include a font-weight', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ fontSize: {
+ sm: '12px',
+ md: ['16px', { lineHeight: '24px', fontWeight: 500 }],
+ lg: ['20px', { lineHeight: '28px', fontWeight: 'bold' }],
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .text-lg {
+ font-size: 20px;
+ font-weight: bold;
+ line-height: 28px;
+ }
+ .text-md {
+ font-size: 16px;
+ font-weight: 500;
+ line-height: 24px;
+ }
+ .text-sm {
+ font-size: 12px;
+ }
+ `)
+ })
+})
+
+test('font-size utilities can include a line-height modifier', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ theme: {
+ fontSize: {
+ sm: ['12px', '20px'],
+ base: ['16px', '24px'],
+ },
+ lineHeight: {
+ 6: '24px',
+ 7: '28px',
+ 8: '32px',
+ },
+ },
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .text-\[13px\]\/6 {
+ font-size: 13px;
+ line-height: 24px;
+ }
+ .text-\[17px\]\/\[23px\] {
+ font-size: 17px;
+ line-height: 23px;
+ }
+ .text-sm {
+ font-size: 12px;
+ line-height: 20px;
+ }
+ .text-sm\/6 {
+ font-size: 12px;
+ line-height: 24px;
+ }
+ .text-sm\/\[21px\] {
+ font-size: 12px;
+ line-height: 21px;
+ }
+ @media (min-width: 768px) {
+ .md\:text-\[19px\]\/8 {
+ font-size: 19px;
+ line-height: 32px;
+ }
+ .md\:text-\[21px\]\/\[29px\] {
+ font-size: 21px;
+ line-height: 29px;
+ }
+ .md\:text-base {
+ font-size: 16px;
+ line-height: 24px;
+ }
+ .md\:text-base\/7 {
+ font-size: 16px;
+ line-height: 28px;
+ }
+ .md\:text-base\/\[33px\] {
+ font-size: 16px;
+ line-height: 33px;
+ }
+ }
+ `)
})
})
diff --git a/tests/plugins/gradientColorStops.test.js b/tests/plugins/gradientColorStops.test.js
index de79cc8fc..c9647d50f 100644
--- a/tests/plugins/gradientColorStops.test.js
+++ b/tests/plugins/gradientColorStops.test.js
@@ -1,191 +1,155 @@
-import { crosscheck, run, html, css } from '../util/run'
+import { run, html, css } from '../util/run'
-crosscheck(({ stable, oxide }) => {
- test('opacity variables are given to colors defined as closures', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- theme: {
- colors: {
- primary: ({ opacityVariable, opacityValue }) => {
- if (opacityValue !== undefined) {
- return `rgba(31,31,31,${opacityValue})`
- }
-
- if (opacityVariable !== undefined) {
- return `rgba(31,31,31,var(${opacityVariable},1))`
- }
-
- return `rgb(31,31,31)`
- },
- secondary: 'hsl(10, 50%, 50%)',
- },
- opacity: {
- 50: '0.5',
- },
+test('opacity variables are given to colors defined as closures', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
},
- }
+ ],
+ theme: {
+ colors: {
+ primary: ({ opacityVariable, opacityValue }) => {
+ if (opacityValue !== undefined) {
+ return `rgba(31,31,31,${opacityValue})`
+ }
- return run('@tailwind utilities', config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .from-primary {
- --tw-gradient-from: #1f1f1f var(--tw-gradient-from-position);
- --tw-gradient-to: #1f1f1f00 var(--tw-gradient-to-position);
- --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
- }
- .from-secondary {
- --tw-gradient-from: #bf5540 var(--tw-gradient-from-position);
- --tw-gradient-to: #bf554000 var(--tw-gradient-to-position);
- --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
- }
- .via-primary {
- --tw-gradient-to: #1f1f1f00 var(--tw-gradient-to-position);
- --tw-gradient-stops: var(--tw-gradient-from), #1f1f1f var(--tw-gradient-via-position),
- var(--tw-gradient-to);
- }
- .via-secondary {
- --tw-gradient-to: #bf554000 var(--tw-gradient-to-position);
- --tw-gradient-stops: var(--tw-gradient-from), #bf5540 var(--tw-gradient-via-position),
- var(--tw-gradient-to);
- }
- .to-primary {
- --tw-gradient-to: #1f1f1f var(--tw-gradient-to-position);
- }
- .to-secondary {
- --tw-gradient-to: #bf5540 var(--tw-gradient-to-position);
- }
- .text-primary {
- --tw-text-opacity: 1;
- color: rgba(31, 31, 31, var(--tw-text-opacity));
- }
- .text-secondary {
- --tw-text-opacity: 1;
- color: hsl(10 50% 50% / var(--tw-text-opacity));
- }
- .text-opacity-50 {
- --tw-text-opacity: 0.5;
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .from-primary {
- --tw-gradient-from: #1f1f1f var(--tw-gradient-from-position);
- --tw-gradient-to: #1f1f1f00 var(--tw-gradient-to-position);
- --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
- }
- .from-secondary {
- --tw-gradient-from: #bf5540 var(--tw-gradient-from-position);
- --tw-gradient-to: #bf554000 var(--tw-gradient-to-position);
- --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
- }
- .via-primary {
- --tw-gradient-to: #1f1f1f00 var(--tw-gradient-to-position);
- --tw-gradient-stops: var(--tw-gradient-from), #1f1f1f var(--tw-gradient-via-position),
- var(--tw-gradient-to);
- }
- .via-secondary {
- --tw-gradient-to: #bf554000 var(--tw-gradient-to-position);
- --tw-gradient-stops: var(--tw-gradient-from), #bf5540 var(--tw-gradient-via-position),
- var(--tw-gradient-to);
- }
- .to-primary {
- --tw-gradient-to: #1f1f1f var(--tw-gradient-to-position);
- }
- .to-secondary {
- --tw-gradient-to: #bf5540 var(--tw-gradient-to-position);
- }
- .text-primary {
- color: #1f1f1f;
- }
- .text-secondary {
- color: #bf5540;
- }
- `)
- })
- })
+ if (opacityVariable !== undefined) {
+ return `rgba(31,31,31,var(${opacityVariable},1))`
+ }
- test('gradient color stop position', () => {
- let config = {
- content: [
- {
- raw: html`
-
- `,
+ return `rgb(31,31,31)`
},
- ],
- theme: {},
- }
+ secondary: 'hsl(10, 50%, 50%)',
+ },
+ opacity: {
+ 50: '0.5',
+ },
+ },
+ }
- return run('@tailwind utilities', config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .bg-gradient-to-r {
- background-image: linear-gradient(to right, var(--tw-gradient-stops));
- }
- .from-\[--from-value\] {
- --tw-gradient-from: var(--from-value) var(--tw-gradient-from-position);
- --tw-gradient-to: #fff0 var(--tw-gradient-to-position);
- --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
- }
- .from-red-500 {
- --tw-gradient-from: #ef4444 var(--tw-gradient-from-position);
- --tw-gradient-to: #ef444400 var(--tw-gradient-to-position);
- --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
- }
- .from-10\% {
- --tw-gradient-from-position: 10%;
- }
- .from-\[11\%\] {
- --tw-gradient-from-position: 11%;
- }
- .from-\[12px\] {
- --tw-gradient-from-position: 12px;
- }
- .via-\[--via-value\] {
- --tw-gradient-to: #fff0 var(--tw-gradient-to-position);
- --tw-gradient-stops: var(--tw-gradient-from),
- var(--via-value) var(--tw-gradient-via-position), var(--tw-gradient-to);
- }
- .via-pink-500 {
- --tw-gradient-to: #ec489900 var(--tw-gradient-to-position);
- --tw-gradient-stops: var(--tw-gradient-from), #ec4899 var(--tw-gradient-via-position),
- var(--tw-gradient-to);
- }
- .via-20\% {
- --tw-gradient-via-position: 20%;
- }
- .via-\[12\%\] {
- --tw-gradient-via-position: 12%;
- }
- .via-\[123px\] {
- --tw-gradient-via-position: 123px;
- }
- .to-\[--to-value\] {
- --tw-gradient-to: var(--to-value) var(--tw-gradient-to-position);
- }
- .to-violet-400 {
- --tw-gradient-to: #a78bfa var(--tw-gradient-to-position);
- }
- .to-30\% {
- --tw-gradient-to-position: 30%;
- }
- .to-\[13\%\] {
- --tw-gradient-to-position: 13%;
- }
- .to-\[14px\] {
- --tw-gradient-to-position: 14px;
- }
- `)
- })
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .from-primary {
+ --tw-gradient-from: #1f1f1f var(--tw-gradient-from-position);
+ --tw-gradient-to: #1f1f1f00 var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+ }
+ .from-secondary {
+ --tw-gradient-from: #bf5540 var(--tw-gradient-from-position);
+ --tw-gradient-to: #bf554000 var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+ }
+ .via-primary {
+ --tw-gradient-to: #1f1f1f00 var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), #1f1f1f var(--tw-gradient-via-position),
+ var(--tw-gradient-to);
+ }
+ .via-secondary {
+ --tw-gradient-to: #bf554000 var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), #bf5540 var(--tw-gradient-via-position),
+ var(--tw-gradient-to);
+ }
+ .to-primary {
+ --tw-gradient-to: #1f1f1f var(--tw-gradient-to-position);
+ }
+ .to-secondary {
+ --tw-gradient-to: #bf5540 var(--tw-gradient-to-position);
+ }
+ .text-primary {
+ --tw-text-opacity: 1;
+ color: rgba(31, 31, 31, var(--tw-text-opacity));
+ }
+ .text-secondary {
+ --tw-text-opacity: 1;
+ color: hsl(10 50% 50% / var(--tw-text-opacity));
+ }
+ .text-opacity-50 {
+ --tw-text-opacity: 0.5;
+ }
+ `)
+ })
+})
+
+test('gradient color stop position', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+ `,
+ },
+ ],
+ theme: {},
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-gradient-to-r {
+ background-image: linear-gradient(to right, var(--tw-gradient-stops));
+ }
+ .from-\[--from-value\] {
+ --tw-gradient-from: var(--from-value) var(--tw-gradient-from-position);
+ --tw-gradient-to: #fff0 var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+ }
+ .from-red-500 {
+ --tw-gradient-from: #ef4444 var(--tw-gradient-from-position);
+ --tw-gradient-to: #ef444400 var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
+ }
+ .from-10\% {
+ --tw-gradient-from-position: 10%;
+ }
+ .from-\[11\%\] {
+ --tw-gradient-from-position: 11%;
+ }
+ .from-\[12px\] {
+ --tw-gradient-from-position: 12px;
+ }
+ .via-\[--via-value\] {
+ --tw-gradient-to: #fff0 var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from),
+ var(--via-value) var(--tw-gradient-via-position), var(--tw-gradient-to);
+ }
+ .via-pink-500 {
+ --tw-gradient-to: #ec489900 var(--tw-gradient-to-position);
+ --tw-gradient-stops: var(--tw-gradient-from), #ec4899 var(--tw-gradient-via-position),
+ var(--tw-gradient-to);
+ }
+ .via-20\% {
+ --tw-gradient-via-position: 20%;
+ }
+ .via-\[12\%\] {
+ --tw-gradient-via-position: 12%;
+ }
+ .via-\[123px\] {
+ --tw-gradient-via-position: 123px;
+ }
+ .to-\[--to-value\] {
+ --tw-gradient-to: var(--to-value) var(--tw-gradient-to-position);
+ }
+ .to-violet-400 {
+ --tw-gradient-to: #a78bfa var(--tw-gradient-to-position);
+ }
+ .to-30\% {
+ --tw-gradient-to-position: 30%;
+ }
+ .to-\[13\%\] {
+ --tw-gradient-to-position: 13%;
+ }
+ .to-\[14px\] {
+ --tw-gradient-to-position: 14px;
+ }
+ `)
})
})
diff --git a/tests/postcss-plugins/nesting/index.test.js b/tests/postcss-plugins/nesting/index.test.js
index fe942a284..d2c339bf8 100644
--- a/tests/postcss-plugins/nesting/index.test.js
+++ b/tests/postcss-plugins/nesting/index.test.js
@@ -19,7 +19,7 @@ it('should be possible to load a custom nesting plugin', async () => {
rule.selector += '-modified'
})
})
- ).toMatchCss(css`
+ ).toMatchFormattedCss(css`
.foo-modified {
color: black;
@media screen(md) {
@@ -39,7 +39,7 @@ it('should be possible to load a custom nesting plugin by name (string) instead'
}
`
- expect(await run(input, 'postcss-nested')).toMatchCss(css`
+ expect(await run(input, 'postcss-nested')).toMatchFormattedCss(css`
.foo {
color: black;
}
@@ -62,7 +62,7 @@ it('should default to the bundled postcss-nested plugin (no options)', async ()
}
`
- expect(await run(input)).toMatchCss(css`
+ expect(await run(input)).toMatchFormattedCss(css`
.foo {
color: black;
}
@@ -85,7 +85,7 @@ it('should default to the bundled postcss-nested plugin (empty options)', async
}
`
- expect(await run(input, {})).toMatchCss(css`
+ expect(await run(input, {})).toMatchFormattedCss(css`
.foo {
color: black;
}
@@ -108,7 +108,7 @@ it('should be possible to use postcss-nested plugin with options', async () => {
}
`
- expect(await run(input, postcssNested({ noIsPseudoSelector: true }))).toMatchCss(css`
+ expect(await run(input, postcssNested({ noIsPseudoSelector: true }))).toMatchFormattedCss(css`
.foo {
color: black;
}
@@ -131,7 +131,7 @@ test('@screen rules are replaced with media queries', async () => {
}
`
- expect(await run(input, postcssNested)).toMatchCss(css`
+ expect(await run(input, postcssNested)).toMatchFormattedCss(css`
.foo {
color: black;
}
@@ -154,7 +154,7 @@ test('@screen rules can work with `@apply`', async () => {
}
`
- expect(await run(input, postcssNested)).toMatchCss(css`
+ expect(await run(input, postcssNested)).toMatchFormattedCss(css`
.foo {
@apply bg-black;
}
@@ -172,7 +172,7 @@ test('nesting does not break downstream plugin visitors', async () => {
.foo {
color: black;
}
- @suppoerts (color: blue) {
+ @supports (color: blue) {
.foo {
color: blue;
}
@@ -186,15 +186,16 @@ test('nesting does not break downstream plugin visitors', async () => {
let result = await run(input, plugins)
- expect(result).toMatchCss(css`
+ expect(result).toMatchFormattedCss(css`
.foo {
- color: #000;
+ color: black;
}
- @suppoerts (color: blue) {
+ @supports (color: blue) {
.foo {
color: blue;
}
}
+ /* Comment */
`)
expect(spyPlugin.spies.Once).toHaveBeenCalled()
diff --git a/tests/prefers-contrast.test.js b/tests/prefers-contrast.test.js
index e3b940da8..52420ee1e 100644
--- a/tests/prefers-contrast.test.js
+++ b/tests/prefers-contrast.test.js
@@ -1,102 +1,71 @@
-import { crosscheck, run, html, css, defaults } from './util/run'
+import { run, html, css, defaults } from './util/run'
-crosscheck(({ stable, oxide }) => {
- it('should be possible to use contrast-more and contrast-less variants', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: { preflight: false },
- }
+it('should be possible to use contrast-more and contrast-less variants', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- .bg-white {
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults}
+ .bg-white {
+ --tw-bg-opacity: 1;
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity));
+ }
+ @media (prefers-contrast: more) {
+ .contrast-more\:bg-pink-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(236 72 153 / var(--tw-bg-opacity));
+ }
+ }
+ @media (prefers-contrast: less) {
+ .contrast-less\:bg-black {
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 0 0 / var(--tw-bg-opacity));
+ }
+ }
+ `)
+ })
+})
+
+it('dark mode should appear after the contrast 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}
+ @media (prefers-contrast: more) {
+ .contrast-more\:bg-black {
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 0 0 / var(--tw-bg-opacity));
+ }
+ }
+ @media (prefers-color-scheme: dark) {
+ .dark\:bg-white {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
- @media (prefers-contrast: more) {
- .contrast-more\:bg-pink-500 {
- --tw-bg-opacity: 1;
- background-color: rgb(236 72 153 / var(--tw-bg-opacity));
- }
- }
- @media (prefers-contrast: less) {
- .contrast-less\:bg-black {
- --tw-bg-opacity: 1;
- background-color: rgb(0 0 0 / var(--tw-bg-opacity));
- }
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- .bg-white {
- background-color: #fff;
- }
- @media (prefers-contrast: more) {
- .contrast-more\:bg-pink-500 {
- background-color: #ec4899;
- }
- }
- @media (prefers-contrast: less) {
- .contrast-less\:bg-black {
- background-color: #000;
- }
- }
- `)
- })
- })
-
- it('dark mode should appear after the contrast variants', () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- @media (prefers-contrast: more) {
- .contrast-more\:bg-black {
- --tw-bg-opacity: 1;
- background-color: rgb(0 0 0 / var(--tw-bg-opacity));
- }
- }
- @media (prefers-color-scheme: dark) {
- .dark\:bg-white {
- --tw-bg-opacity: 1;
- background-color: rgb(255 255 255 / var(--tw-bg-opacity));
- }
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- @media (prefers-contrast: more) {
- .contrast-more\:bg-black {
- background-color: #000;
- }
- }
- @media (prefers-color-scheme: dark) {
- .dark\:bg-white {
- background-color: #fff;
- }
- }
- `)
- })
+ }
+ `)
})
})
diff --git a/tests/prefix.test.js b/tests/prefix.test.js
index ea3d041ff..c9c0e5f25 100644
--- a/tests/prefix.test.js
+++ b/tests/prefix.test.js
@@ -1,625 +1,609 @@
-import { crosscheck, run, html, css, defaults } from './util/run'
+import { run, html, css, defaults } from './util/run'
-crosscheck(({ stable, oxide }) => {
- oxide.test.todo('prefix')
- stable.test('prefix', () => {
- let config = {
- prefix: 'tw-',
- darkMode: 'class',
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- theme: {
- animation: {
- spin: 'spin 1s linear infinite',
- ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
- },
- keyframes: {
- spin: { to: { transform: 'rotate(360deg)' } },
- },
+test('prefix', () => {
+ let config = {
+ prefix: 'tw-',
+ darkMode: 'class',
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
},
- plugins: [
- function ({ addComponents, addUtilities }) {
- addComponents({
- '.btn-prefix': {
+ ],
+ corePlugins: { preflight: false },
+ theme: {
+ animation: {
+ spin: 'spin 1s linear infinite',
+ ping: 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
+ },
+ keyframes: {
+ spin: { to: { transform: 'rotate(360deg)' } },
+ },
+ },
+ plugins: [
+ function ({ addComponents, addUtilities }) {
+ addComponents({
+ '.btn-prefix': {
+ button: 'yes',
+ },
+ })
+ addComponents(
+ {
+ '.btn-no-prefix': {
button: 'yes',
},
- })
- addComponents(
- {
- '.btn-no-prefix': {
- button: 'yes',
- },
- },
- { respectPrefix: false }
- )
- addUtilities({
- '.custom-util-prefix': {
+ },
+ { respectPrefix: false }
+ )
+ addUtilities({
+ '.custom-util-prefix': {
+ button: 'no',
+ },
+ })
+ addUtilities(
+ {
+ '.custom-util-no-prefix': {
button: 'no',
},
- })
- addUtilities(
- {
- '.custom-util-no-prefix': {
- button: 'no',
- },
- },
- { respectPrefix: false }
- )
- },
- ],
- }
-
- let input = css`
- @tailwind base;
- @tailwind components;
- @layer components {
- .custom-component {
- @apply tw-font-bold dark:group-hover:tw-font-normal;
- }
- }
- @tailwind utilities;
- @layer utilities {
- .custom-utility {
- foo: bar;
- }
- }
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- .tw-container {
- width: 100%;
- }
- @media (min-width: 640px) {
- .tw-container {
- max-width: 640px;
- }
- }
- @media (min-width: 768px) {
- .tw-container {
- max-width: 768px;
- }
- }
- @media (min-width: 1024px) {
- .tw-container {
- max-width: 1024px;
- }
- }
- @media (min-width: 1280px) {
- .tw-container {
- max-width: 1280px;
- }
- }
- @media (min-width: 1536px) {
- .tw-container {
- max-width: 1536px;
- }
- }
- .tw-btn-prefix,
- .btn-no-prefix {
- button: yes;
- }
- .custom-component {
- font-weight: 700;
- }
- :is(.tw-dark .tw-group:hover .custom-component) {
- font-weight: 400;
- }
- .tw--ml-4 {
- margin-left: -1rem;
- }
- .tw-animate-ping {
- animation: 1s cubic-bezier(0, 0, 0.2, 1) infinite ping;
- }
- @keyframes tw-spin {
- to {
- transform: rotate(360deg);
- }
- }
- .tw-animate-spin {
- animation: 1s linear infinite tw-spin;
- }
- .tw-font-bold {
- font-weight: 700;
- }
- .tw-custom-util-prefix,
- .custom-util-no-prefix {
- button: no;
- }
- .tw-group:hover .group-hover\:focus-within\:tw-text-left:focus-within {
- text-align: left;
- }
- :is([dir='rtl'] .rtl\:active\:tw-text-center:active) {
- text-align: center;
- }
- @media (prefers-reduced-motion: no-preference) {
- .motion-safe\:hover\:tw-text-center:hover {
- text-align: center;
- }
- }
- :is(.tw-dark .dark\:tw-bg-\[rgb\(255\,0\,0\)\]) {
- --tw-bg-opacity: 1;
- background-color: rgb(255 0 0 / var(--tw-bg-opacity));
- }
- :is(.tw-dark .dark\:focus\:tw-text-left:focus) {
- text-align: left;
- }
- @media (min-width: 768px) {
- .md\:tw--ml-5 {
- margin-left: -1.25rem;
- }
- .md\:hover\:tw--ml-6:hover {
- margin-left: -1.5rem;
- }
- .md\:hover\:tw-text-right:hover {
- text-align: right;
- }
- }
- `)
- })
- })
-
- oxide.test.todo('negative values: marker before prefix')
- stable.test('negative values: marker before prefix', async () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- await run(input, config)
-
- const result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .-tw-top-1 {
- top: -0.25rem;
- }
- `)
- })
-
- oxide.test.todo('negative values: marker after prefix')
- stable.test('negative values: marker after prefix', async () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- await run(input, config)
-
- const result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .tw--top-1 {
- top: -0.25rem;
- }
- `)
- })
-
- oxide.test.todo('negative values: marker before prefix and arbitrary value')
- stable.test('negative values: marker before prefix and arbitrary value', async () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- await run(input, config)
-
- const result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .-tw-top-\[1px\] {
- top: -1px;
- }
- `)
- })
-
- oxide.test.todo('negative values: marker after prefix and arbitrary value')
- stable.test('negative values: marker after prefix and arbitrary value', async () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- await run(input, config)
-
- const result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .tw--top-\[1px\] {
- top: -1px;
- }
- `)
- })
-
- oxide.test.todo('negative values: no marker and arbitrary value')
- stable.test('negative values: no marker and arbitrary value', async () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- await run(input, config)
-
- const result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .tw-top-\[-1px\] {
- top: -1px;
- }
- `)
- })
-
- oxide.test.todo('negative values: variant versions')
- stable.test('negative values: variant versions', async () => {
- let config = {
- prefix: 'tw-',
- content: [
- {
- raw: html`
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- await run(input, config)
-
- const result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .hover\:-tw-top-1:hover {
- top: -0.25rem;
- }
- .hover\:-tw-top-\[1px\]:hover {
- top: -1px;
- }
- .hover\:tw--top-1:hover {
- top: -0.25rem;
- }
- .hover\:tw--top-\[1px\]:hover,
- .hover\:tw-top-\[-1px\]:hover {
- top: -1px;
- }
- `)
- })
-
- oxide.test.todo('negative values: prefix and apply')
- stable.test('negative values: prefix and apply', async () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
-
- .a {
- @apply hover:tw--top-1;
- }
- .b {
- @apply hover:-tw-top-1;
- }
- .c {
- @apply hover:-tw-top-[1px];
- }
- .d {
- @apply hover:tw--top-[1px];
- }
- .e {
- @apply hover:tw-top-[-1px];
- }
- `
-
- await run(input, config)
-
- const result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .a:hover,
- .b:hover {
- top: -0.25rem;
- }
- .c:hover,
- .d:hover,
- .e:hover {
- top: -1px;
- }
- `)
- })
-
- oxide.test.todo('negative values: prefix in the safelist')
- stable.test('negative values: prefix in the safelist', async () => {
- let config = {
- prefix: 'tw-',
- safelist: [{ pattern: /-tw-top-1/g }, { pattern: /tw--top-1/g }],
- theme: {
- inset: {
- 1: '0.25rem',
- },
+ },
+ { respectPrefix: false }
+ )
},
- content: [{ raw: html`` }],
- corePlugins: { preflight: false },
- }
+ ],
+ }
- let input = css`
- @tailwind utilities;
- `
-
- await run(input, config)
-
- const result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .-tw-top-1,
- .tw--top-1 {
- top: -0.25rem;
+ let input = css`
+ @tailwind base;
+ @tailwind components;
+ @layer components {
+ .custom-component {
+ @apply tw-font-bold dark:group-hover:tw-font-normal;
}
- `)
- })
-
- oxide.test.todo('prefix with negative values and variants in the safelist')
- stable.test('prefix with negative values and variants in the safelist', async () => {
- let config = {
- prefix: 'tw-',
- safelist: [
- { pattern: /-tw-top-1/, variants: ['hover', 'sm:hover'] },
- { pattern: /tw--top-1/, variants: ['hover', 'sm:hover'] },
- ],
- theme: {
- inset: {
- 1: '0.25rem',
- },
- },
- content: [{ raw: html`` }],
- corePlugins: { preflight: false },
}
+ @tailwind utilities;
+ @layer utilities {
+ .custom-utility {
+ foo: bar;
+ }
+ }
+ `
- let input = css`
- @tailwind utilities;
- `
-
- await run(input, config)
-
- const result = await run(input, config)
-
+ return run(input, config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
- .-tw-top-1,
- .tw--top-1,
- .hover\:-tw-top-1:hover,
- .hover\:tw--top-1:hover {
- top: -0.25rem;
+ ${defaults}
+ .tw-container {
+ width: 100%;
}
@media (min-width: 640px) {
- .sm\:hover\:-tw-top-1:hover,
- .sm\:hover\:tw--top-1:hover {
- top: -0.25rem;
+ .tw-container {
+ max-width: 640px;
}
}
- `)
- })
-
- oxide.test.todo('prefix does not detect and generate unnecessary classes')
- stable.test('prefix does not detect and generate unnecessary classes', async () => {
- let config = {
- prefix: 'tw-_',
- content: [{ raw: html`-aaa-filter aaaa-table aaaa-hidden` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- const result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css``)
- })
-
- oxide.test.todo('supports prefixed utilities using arbitrary values')
- stable.test('supports prefixed utilities using arbitrary values', async () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`foo` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- .foo {
- @apply tw-text-[color:rgb(var(--button-background,var(--primary-button-background)))];
- @apply tw-ease-[cubic-bezier(0.77,0,0.175,1)];
- @apply tw-rounded-[min(4px,var(--input-border-radius))];
- }
- `
-
- const result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .foo {
- color: rgb(var(--button-background, var(--primary-button-background)));
- border-radius: min(4px, var(--input-border-radius));
- transition-timing-function: cubic-bezier(0.77, 0, 0.175, 1);
- }
- `)
- })
-
- oxide.test.todo('supports non-word prefixes (1)')
- stable.test('supports non-word prefixes (1)', async () => {
- let config = {
- prefix: '@',
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- @layer utilities {
- .my-utility {
- color: orange;
+ @media (min-width: 768px) {
+ .tw-container {
+ max-width: 768px;
}
}
- .foo {
- @apply @text-white;
- @apply [background-color:red];
+ @media (min-width: 1024px) {
+ .tw-container {
+ max-width: 1024px;
+ }
}
- `
-
- const result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .\@bg-black {
+ @media (min-width: 1280px) {
+ .tw-container {
+ max-width: 1280px;
+ }
+ }
+ @media (min-width: 1536px) {
+ .tw-container {
+ max-width: 1536px;
+ }
+ }
+ .tw-btn-prefix,
+ .btn-no-prefix {
+ button: yes;
+ }
+ .custom-component {
+ font-weight: 700;
+ }
+ :is(.tw-dark .tw-group:hover .custom-component) {
+ font-weight: 400;
+ }
+ .tw--ml-4 {
+ margin-left: -1rem;
+ }
+ .tw-animate-ping {
+ animation: 1s cubic-bezier(0, 0, 0.2, 1) infinite ping;
+ }
+ @keyframes tw-spin {
+ to {
+ transform: rotate(360deg);
+ }
+ }
+ .tw-animate-spin {
+ animation: 1s linear infinite tw-spin;
+ }
+ .tw-font-bold {
+ font-weight: 700;
+ }
+ .tw-custom-util-prefix,
+ .custom-util-no-prefix {
+ button: no;
+ }
+ .tw-group:hover .group-hover\:focus-within\:tw-text-left:focus-within {
+ text-align: left;
+ }
+ :is([dir='rtl'] .rtl\:active\:tw-text-center:active) {
+ text-align: center;
+ }
+ @media (prefers-reduced-motion: no-preference) {
+ .motion-safe\:hover\:tw-text-center:hover {
+ text-align: center;
+ }
+ }
+ :is(.tw-dark .dark\:tw-bg-\[rgb\(255\,0\,0\)\]) {
--tw-bg-opacity: 1;
- background-color: rgb(0 0 0 / var(--tw-bg-opacity));
+ background-color: rgb(255 0 0 / var(--tw-bg-opacity));
}
- .\@underline {
- text-decoration-line: underline;
+ :is(.tw-dark .dark\:focus\:tw-text-left:focus) {
+ text-align: left;
}
- .my-utility {
- color: orange;
- }
- .foo {
- --tw-text-opacity: 1;
- color: rgb(255 255 255 / var(--tw-text-opacity));
- background-color: red;
- }
- .hover\:before\:\@content-\[\'Hovering\'\]:hover:before {
- --tw-content: 'Hovering';
- content: var(--tw-content);
- }
- `)
- })
-
- oxide.test.todo('supports non-word prefixes (2)')
- stable.test('supports non-word prefixes (2)', async () => {
- let config = {
- prefix: '@]$',
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- @layer utilities {
- .my-utility {
- color: orange;
+ @media (min-width: 768px) {
+ .md\:tw--ml-5 {
+ margin-left: -1.25rem;
+ }
+ .md\:hover\:tw--ml-6:hover {
+ margin-left: -1.5rem;
+ }
+ .md\:hover\:tw-text-right:hover {
+ text-align: right;
}
- }
- .foo {
- @apply @]$text-white;
- @apply [background-color:red];
- }
- `
-
- const result = await run(input, config)
-
- // TODO: The class `.hover\:before\:\@\]\$content-\[\'Hovering\'\]:hover::before` is not generated
- // This happens because of the parenthesis/brace/bracket clipping performed on candidates
-
- expect(result.css).toMatchFormattedCss(css`
- .\@\]\$bg-black {
- --tw-bg-opacity: 1;
- background-color: rgb(0 0 0 / var(--tw-bg-opacity));
- }
- .\@\]\$underline {
- text-decoration-line: underline;
- }
- .my-utility {
- color: orange;
- }
- .foo {
- --tw-text-opacity: 1;
- color: rgb(255 255 255 / var(--tw-text-opacity));
- background-color: red;
}
`)
})
})
+
+test('negative values: marker before prefix', async () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ await run(input, config)
+
+ const result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .-tw-top-1 {
+ top: -0.25rem;
+ }
+ `)
+})
+
+test('negative values: marker after prefix', async () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ await run(input, config)
+
+ const result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .tw--top-1 {
+ top: -0.25rem;
+ }
+ `)
+})
+
+test('negative values: marker before prefix and arbitrary value', async () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ await run(input, config)
+
+ const result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .-tw-top-\[1px\] {
+ top: -1px;
+ }
+ `)
+})
+
+test('negative values: marker after prefix and arbitrary value', async () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ await run(input, config)
+
+ const result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .tw--top-\[1px\] {
+ top: -1px;
+ }
+ `)
+})
+
+test('negative values: no marker and arbitrary value', async () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ await run(input, config)
+
+ const result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .tw-top-\[-1px\] {
+ top: -1px;
+ }
+ `)
+})
+
+test('negative values: variant versions', async () => {
+ let config = {
+ prefix: 'tw-',
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ await run(input, config)
+
+ const result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .hover\:-tw-top-1:hover {
+ top: -0.25rem;
+ }
+ .hover\:-tw-top-\[1px\]:hover {
+ top: -1px;
+ }
+ .hover\:tw--top-1:hover {
+ top: -0.25rem;
+ }
+ .hover\:tw--top-\[1px\]:hover,
+ .hover\:tw-top-\[-1px\]:hover {
+ top: -1px;
+ }
+ `)
+})
+
+test('negative values: prefix and apply', async () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+
+ .a {
+ @apply hover:tw--top-1;
+ }
+ .b {
+ @apply hover:-tw-top-1;
+ }
+ .c {
+ @apply hover:-tw-top-[1px];
+ }
+ .d {
+ @apply hover:tw--top-[1px];
+ }
+ .e {
+ @apply hover:tw-top-[-1px];
+ }
+ `
+
+ await run(input, config)
+
+ const result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .a:hover,
+ .b:hover {
+ top: -0.25rem;
+ }
+ .c:hover,
+ .d:hover,
+ .e:hover {
+ top: -1px;
+ }
+ `)
+})
+
+test('negative values: prefix in the safelist', async () => {
+ let config = {
+ prefix: 'tw-',
+ safelist: [{ pattern: /-tw-top-1/g }, { pattern: /tw--top-1/g }],
+ theme: {
+ inset: {
+ 1: '0.25rem',
+ },
+ },
+ content: [{ raw: html`` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ await run(input, config)
+
+ const result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .-tw-top-1,
+ .tw--top-1 {
+ top: -0.25rem;
+ }
+ `)
+})
+
+test('prefix with negative values and variants in the safelist', async () => {
+ let config = {
+ prefix: 'tw-',
+ safelist: [
+ { pattern: /-tw-top-1/, variants: ['hover', 'sm:hover'] },
+ { pattern: /tw--top-1/, variants: ['hover', 'sm:hover'] },
+ ],
+ theme: {
+ inset: {
+ 1: '0.25rem',
+ },
+ },
+ content: [{ raw: html`` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ await run(input, config)
+
+ const result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .-tw-top-1,
+ .tw--top-1,
+ .hover\:-tw-top-1:hover,
+ .hover\:tw--top-1:hover {
+ top: -0.25rem;
+ }
+ @media (min-width: 640px) {
+ .sm\:hover\:-tw-top-1:hover,
+ .sm\:hover\:tw--top-1:hover {
+ top: -0.25rem;
+ }
+ }
+ `)
+})
+
+test('prefix does not detect and generate unnecessary classes', async () => {
+ let config = {
+ prefix: 'tw-_',
+ content: [{ raw: html`-aaa-filter aaaa-table aaaa-hidden` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ const result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css``)
+})
+
+test('supports prefixed utilities using arbitrary values', async () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`foo` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ .foo {
+ @apply tw-text-[color:rgb(var(--button-background,var(--primary-button-background)))];
+ @apply tw-ease-[cubic-bezier(0.77,0,0.175,1)];
+ @apply tw-rounded-[min(4px,var(--input-border-radius))];
+ }
+ `
+
+ const result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ color: rgb(var(--button-background, var(--primary-button-background)));
+ border-radius: min(4px, var(--input-border-radius));
+ transition-timing-function: cubic-bezier(0.77, 0, 0.175, 1);
+ }
+ `)
+})
+
+test('supports non-word prefixes (1)', async () => {
+ let config = {
+ prefix: '@',
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ @layer utilities {
+ .my-utility {
+ color: orange;
+ }
+ }
+ .foo {
+ @apply @text-white;
+ @apply [background-color:red];
+ }
+ `
+
+ const result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .\@bg-black {
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 0 0 / var(--tw-bg-opacity));
+ }
+ .\@underline {
+ text-decoration-line: underline;
+ }
+ .my-utility {
+ color: orange;
+ }
+ .foo {
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity));
+ background-color: red;
+ }
+ .hover\:before\:\@content-\[\'Hovering\'\]:hover:before {
+ --tw-content: 'Hovering';
+ content: var(--tw-content);
+ }
+ `)
+})
+
+test('supports non-word prefixes (2)', async () => {
+ let config = {
+ prefix: '@]$',
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ @layer utilities {
+ .my-utility {
+ color: orange;
+ }
+ }
+ .foo {
+ @apply @]$text-white;
+ @apply [background-color:red];
+ }
+ `
+
+ const result = await run(input, config)
+
+ // TODO: The class `.hover\:before\:\@\]\$content-\[\'Hovering\'\]:hover::before` is not generated
+ // This happens because of the parenthesis/brace/bracket clipping performed on candidates
+
+ expect(result.css).toMatchFormattedCss(css`
+ .\@\]\$bg-black {
+ --tw-bg-opacity: 1;
+ background-color: rgb(0 0 0 / var(--tw-bg-opacity));
+ }
+ .\@\]\$underline {
+ text-decoration-line: underline;
+ }
+ .my-utility {
+ color: orange;
+ }
+ .foo {
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity));
+ background-color: red;
+ }
+ `)
+})
diff --git a/tests/prefixSelector.test.js b/tests/prefixSelector.test.js
index c7db08d03..92fd6194f 100644
--- a/tests/prefixSelector.test.js
+++ b/tests/prefixSelector.test.js
@@ -1,20 +1,17 @@
import prefix from '../src/util/prefixSelector'
-import { crosscheck } from './util/run'
-crosscheck(() => {
- test('it prefixes classes with the provided prefix', () => {
- expect(prefix('tw-', '.foo')).toEqual('.tw-foo')
- })
-
- test('it properly prefixes selectors with non-standard characters', () => {
- expect(prefix('tw-', '.hello\\:world')).toEqual('.tw-hello\\:world')
- expect(prefix('tw-', '.foo\\/bar')).toEqual('.tw-foo\\/bar')
- expect(prefix('tw-', '.wew\\.lad')).toEqual('.tw-wew\\.lad')
- })
-
- test('it prefixes all classes in a selector', () => {
- expect(prefix('tw-', '.btn-blue .w-1\\/4 > h1.text-xl + a .bar')).toEqual(
- '.tw-btn-blue .tw-w-1\\/4 > h1.tw-text-xl + a .tw-bar'
- )
- })
+test('it prefixes classes with the provided prefix', () => {
+ expect(prefix('tw-', '.foo')).toEqual('.tw-foo')
+})
+
+test('it properly prefixes selectors with non-standard characters', () => {
+ expect(prefix('tw-', '.hello\\:world')).toEqual('.tw-hello\\:world')
+ expect(prefix('tw-', '.foo\\/bar')).toEqual('.tw-foo\\/bar')
+ expect(prefix('tw-', '.wew\\.lad')).toEqual('.tw-wew\\.lad')
+})
+
+test('it prefixes all classes in a selector', () => {
+ expect(prefix('tw-', '.btn-blue .w-1\\/4 > h1.text-xl + a .bar')).toEqual(
+ '.tw-btn-blue .tw-w-1\\/4 > h1.tw-text-xl + a .tw-bar'
+ )
})
diff --git a/tests/preflight.test.js b/tests/preflight.test.js
index 4f6f722c7..ad654999e 100644
--- a/tests/preflight.test.js
+++ b/tests/preflight.test.js
@@ -1,25 +1,23 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(({ stable, oxide }) => {
- it('preflight has a correct border color fallback', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- borderColor: ({ theme }) => theme('colors'),
- },
- plugins: [],
- corePlugins: { preflight: true },
- }
+it('preflight has a correct border color fallback', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ borderColor: ({ theme }) => theme('colors'),
+ },
+ plugins: [],
+ corePlugins: { preflight: true },
+ }
- let input = css`
- @tailwind base;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- stable.expect(result.css).toContain(`border-color: currentColor;`)
- // Lightning CSS optimizes this to just `border-color: 0 solid;` based on the br value.
- oxide.expect(result.css).toContain(`border: 0 solid;`)
- })
+ return run(input, config).then((result) => {
+ // Lightning CSS optimizes this to just `border-color: 0 solid;` because `currentcolor` is the default user-agent stylesheet value.
+ // https://drafts.csswg.org/css-backgrounds/#border-color
+ expect(result.css).toContain(`border: 0 solid;`)
})
})
diff --git a/tests/raw-content.oxide.test.css b/tests/raw-content.oxide.test.css
deleted file mode 100644
index aa5abd127..000000000
--- a/tests/raw-content.oxide.test.css
+++ /dev/null
@@ -1,759 +0,0 @@
-.container {
- width: 100%;
-}
-@media (min-width: 640px) {
- .container {
- max-width: 640px;
- }
-}
-@media (min-width: 768px) {
- .container {
- max-width: 768px;
- }
-}
-@media (min-width: 1024px) {
- .container {
- max-width: 1024px;
- }
-}
-@media (min-width: 1280px) {
- .container {
- max-width: 1280px;
- }
-}
-@media (min-width: 1536px) {
- .container {
- max-width: 1536px;
- }
-}
-.sr-only {
- width: 1px;
- height: 1px;
- clip: rect(0, 0, 0, 0);
- white-space: nowrap;
- border-width: 0;
- margin: -1px;
- padding: 0;
- position: absolute;
- overflow: hidden;
-}
-.pointer-events-none {
- pointer-events: none;
-}
-.invisible {
- visibility: hidden;
-}
-.collapse {
- visibility: collapse;
-}
-.absolute {
- position: absolute;
-}
-.inset-0 {
- inset: 0;
-}
-.inset-x-2 {
- left: 0.5rem;
- right: 0.5rem;
-}
-.inset-y-4 {
- top: 1rem;
- bottom: 1rem;
-}
-.bottom-12 {
- bottom: 3rem;
-}
-.left-16 {
- left: 4rem;
-}
-.right-8 {
- right: 2rem;
-}
-.top-6 {
- top: 1.5rem;
-}
-.isolate {
- isolation: isolate;
-}
-.isolation-auto {
- isolation: auto;
-}
-.z-30 {
- z-index: 30;
-}
-.order-2 {
- order: 2;
-}
-.order-last {
- order: 9999;
-}
-.col-span-3 {
- grid-column: span 3 / span 3;
-}
-.col-start-1 {
- grid-column-start: 1;
-}
-.col-end-4 {
- grid-column-end: 4;
-}
-.row-span-2 {
- grid-row: span 2 / span 2;
-}
-.row-start-3 {
- grid-row-start: 3;
-}
-.row-end-5 {
- grid-row-end: 5;
-}
-.float-right {
- float: right;
-}
-.clear-left {
- clear: left;
-}
-.m-4 {
- margin: 1rem;
-}
-.mx-auto {
- margin-left: auto;
- margin-right: auto;
-}
-.my-2 {
- margin-top: 0.5rem;
- margin-bottom: 0.5rem;
-}
-.mb-3 {
- margin-bottom: 0.75rem;
-}
-.ml-4 {
- margin-left: 1rem;
-}
-.mr-1 {
- margin-right: 0.25rem;
-}
-.mt-0 {
- margin-top: 0;
-}
-.box-border {
- box-sizing: border-box;
-}
-.inline-grid {
- display: inline-grid;
-}
-.hidden {
- display: none;
-}
-.h-16 {
- height: 4rem;
-}
-.max-h-screen {
- max-height: 100vh;
-}
-.min-h-0 {
- min-height: 0;
-}
-.w-12 {
- width: 3rem;
-}
-.min-w-min {
- min-width: min-content;
-}
-.max-w-full {
- max-width: 100%;
-}
-.flex-1 {
- flex: 1;
-}
-.flex-shrink {
- flex-shrink: 1;
-}
-.flex-shrink-0 {
- flex-shrink: 0;
-}
-.flex-grow {
- flex-grow: 1;
-}
-.flex-grow-0 {
- flex-grow: 0;
-}
-.table-fixed {
- table-layout: fixed;
-}
-.caption-top {
- caption-side: top;
-}
-.caption-bottom {
- caption-side: bottom;
-}
-.border-collapse {
- border-collapse: collapse;
-}
-.origin-top-right {
- transform-origin: 100% 0;
-}
-.-translate-x-3 {
- --tw-translate-x: -0.75rem;
- transform: translate(var(--tw-translate-x), 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));
-}
-.-translate-x-4 {
- --tw-translate-x: -1rem;
- transform: translate(var(--tw-translate-x), 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));
-}
-.translate-x-5 {
- --tw-translate-x: 1.25rem;
- transform: translate(var(--tw-translate-x), 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));
-}
-.translate-y-6 {
- --tw-translate-y: 1.5rem;
- transform: translate(var(--tw-translate-x), 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: translate(var(--tw-translate-x), 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));
-}
-.skew-x-12 {
- --tw-skew-x: 12deg;
- transform: translate(var(--tw-translate-x), 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));
-}
-.skew-y-12 {
- --tw-skew-y: 12deg;
- transform: translate(var(--tw-translate-x), 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));
-}
-.scale-95 {
- --tw-scale-x: 0.95;
- --tw-scale-y: 0.95;
- transform: translate(var(--tw-translate-x), 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));
-}
-.transform {
- transform: translate(var(--tw-translate-x), 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));
-}
-.transform-gpu {
- transform: translate3d(var(--tw-translate-x), var(--tw-translate-y), 0) rotate(var(--tw-rotate))
- skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x))
- scaleY(var(--tw-scale-y));
-}
-.animate-none {
- animation: none;
-}
-@keyframes spin {
- to {
- transform: rotate(360deg);
- }
-}
-.animate-spin {
- animation: 1s linear infinite spin;
-}
-.cursor-pointer {
- cursor: pointer;
-}
-.select-none {
- user-select: none;
-}
-.resize-none {
- resize: none;
-}
-.list-inside {
- list-style-position: inside;
-}
-.list-disc {
- list-style-type: disc;
-}
-.appearance-none {
- appearance: none;
-}
-.auto-cols-min {
- grid-auto-columns: min-content;
-}
-.grid-flow-row {
- grid-auto-flow: row;
-}
-.auto-rows-max {
- grid-auto-rows: max-content;
-}
-.grid-cols-4 {
- grid-template-columns: repeat(4, minmax(0, 1fr));
-}
-.grid-rows-3 {
- grid-template-rows: repeat(3, minmax(0, 1fr));
-}
-.flex-row-reverse {
- flex-direction: row-reverse;
-}
-.flex-wrap {
- flex-wrap: wrap;
-}
-.place-content-start {
- place-content: start;
-}
-.place-items-end {
- place-items: end;
-}
-.content-center {
- align-content: center;
-}
-.items-start {
- align-items: flex-start;
-}
-.justify-center {
- justify-content: center;
-}
-.justify-items-end {
- justify-items: end;
-}
-.gap-4 {
- gap: 1rem;
-}
-.gap-x-2 {
- column-gap: 0.5rem;
-}
-.gap-y-3 {
- row-gap: 0.75rem;
-}
-.space-x-4 > :not([hidden]) ~ :not([hidden]) {
- --tw-space-x-reverse: 0;
- margin-inline-start: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
- margin-inline-end: calc(1rem * var(--tw-space-x-reverse));
-}
-.space-y-3 > :not([hidden]) ~ :not([hidden]) {
- --tw-space-y-reverse: 0;
- margin-top: calc(0.75rem * calc(1 - var(--tw-space-y-reverse)));
- margin-bottom: calc(0.75rem * var(--tw-space-y-reverse));
-}
-.space-y-reverse > :not([hidden]) ~ :not([hidden]) {
- --tw-space-y-reverse: 1;
-}
-.space-x-reverse > :not([hidden]) ~ :not([hidden]) {
- --tw-space-x-reverse: 1;
-}
-.divide-x-0 > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-x-reverse: 0;
- border-inline-end-width: calc(0px * var(--tw-divide-x-reverse));
- border-inline-start-width: calc(0px * calc(1 - var(--tw-divide-x-reverse)));
-}
-.divide-x-2 > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-x-reverse: 0;
- border-inline-end-width: calc(2px * var(--tw-divide-x-reverse));
- border-inline-start-width: calc(2px * calc(1 - var(--tw-divide-x-reverse)));
-}
-.divide-y-0 > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-y-reverse: 0;
- border-top-width: calc(0px * calc(1 - var(--tw-divide-y-reverse)));
- border-bottom-width: calc(0px * var(--tw-divide-y-reverse));
-}
-.divide-y-4 > :not([hidden]) ~ :not([hidden]) {
- --tw-divide-y-reverse: 0;
- border-top-width: calc(4px * calc(1 - var(--tw-divide-y-reverse)));
- border-bottom-width: calc(4px * var(--tw-divide-y-reverse));
-}
-.divide-dotted > :not([hidden]) ~ :not([hidden]) {
- border-style: dotted;
-}
-.divide-gray-200 > :not([hidden]) ~ :not([hidden]) {
- border-color: #e5e7eb;
-}
-.place-self-center {
- place-self: center;
-}
-.self-end {
- align-self: flex-end;
-}
-.justify-self-start {
- justify-self: start;
-}
-.overflow-hidden {
- overflow: hidden;
-}
-.overscroll-contain {
- overscroll-behavior: contain;
-}
-.scroll-smooth {
- scroll-behavior: smooth;
-}
-.truncate {
- text-overflow: ellipsis;
- white-space: nowrap;
- overflow: hidden;
-}
-.overflow-ellipsis {
- text-overflow: ellipsis;
-}
-.whitespace-nowrap {
- white-space: nowrap;
-}
-.break-words {
- overflow-wrap: break-word;
-}
-.rounded-md {
- border-radius: 0.375rem;
-}
-.border {
- border-width: 1px;
-}
-.border-2 {
- border-width: 2px;
-}
-.border-solid {
- border-style: solid;
-}
-.border-black {
- border-color: #000;
-}
-.bg-green-500 {
- background-color: #22c55e;
-}
-.bg-gradient-to-r {
- background-image: linear-gradient(to right, var(--tw-gradient-stops));
-}
-.from-red-300 {
- --tw-gradient-from: #fca5a5 var(--tw-gradient-from-position);
- --tw-gradient-to: #fca5a500 var(--tw-gradient-to-position);
- --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
-}
-.via-purple-200 {
- --tw-gradient-to: #e9d5ff00 var(--tw-gradient-to-position);
- --tw-gradient-stops: var(--tw-gradient-from), #e9d5ff var(--tw-gradient-via-position),
- var(--tw-gradient-to);
-}
-.to-blue-400 {
- --tw-gradient-to: #60a5fa var(--tw-gradient-to-position);
-}
-.decoration-slice {
- -webkit-box-decoration-break: slice;
- box-decoration-break: slice;
-}
-.decoration-clone {
- -webkit-box-decoration-break: clone;
- box-decoration-break: clone;
-}
-.bg-cover {
- background-size: cover;
-}
-.bg-local {
- background-attachment: local;
-}
-.bg-clip-border {
- background-clip: border-box;
-}
-.bg-top {
- background-position: top;
-}
-.bg-no-repeat {
- background-repeat: no-repeat;
-}
-.bg-origin-border {
- background-origin: border-box;
-}
-.bg-origin-padding {
- background-origin: padding-box;
-}
-.bg-origin-content {
- background-origin: content-box;
-}
-.fill-current {
- fill: currentColor;
-}
-.stroke-current {
- stroke: currentColor;
-}
-.stroke-2 {
- stroke-width: 2px;
-}
-.object-cover {
- object-fit: cover;
-}
-.object-bottom {
- object-position: bottom;
-}
-.p-4 {
- padding: 1rem;
-}
-.px-3 {
- padding-left: 0.75rem;
- padding-right: 0.75rem;
-}
-.py-2 {
- padding-top: 0.5rem;
- padding-bottom: 0.5rem;
-}
-.pb-3 {
- padding-bottom: 0.75rem;
-}
-.pl-4 {
- padding-left: 1rem;
-}
-.pr-2 {
- padding-right: 0.5rem;
-}
-.pt-1 {
- padding-top: 0.25rem;
-}
-.text-center {
- text-align: center;
-}
-.align-middle {
- vertical-align: middle;
-}
-.font-sans {
- font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
- Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol,
- Noto Color Emoji;
-}
-.text-2xl {
- font-size: 1.5rem;
- line-height: 2rem;
-}
-.font-medium {
- font-weight: 500;
-}
-.uppercase {
- text-transform: uppercase;
-}
-.not-italic {
- font-style: normal;
-}
-.ordinal {
- --tw-ordinal: ordinal;
- font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
- var(--tw-numeric-spacing) var(--tw-numeric-fraction);
-}
-.tabular-nums {
- --tw-numeric-spacing: tabular-nums;
- font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
- var(--tw-numeric-spacing) var(--tw-numeric-fraction);
-}
-.diagonal-fractions {
- --tw-numeric-fraction: diagonal-fractions;
- font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure)
- var(--tw-numeric-spacing) var(--tw-numeric-fraction);
-}
-.leading-5 {
- line-height: 1.25rem;
-}
-.leading-relaxed {
- line-height: 1.625;
-}
-.tracking-tight {
- letter-spacing: -0.025em;
-}
-.text-indigo-500 {
- color: #6366f1;
-}
-.underline {
- text-decoration-line: underline;
-}
-.antialiased {
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-.placeholder-green-300::placeholder {
- color: #86efac;
-}
-.opacity-90 {
- opacity: 0.9;
-}
-.bg-blend-darken {
- background-blend-mode: darken;
-}
-.bg-blend-difference {
- background-blend-mode: difference;
-}
-.mix-blend-multiply {
- mix-blend-mode: multiply;
-}
-.mix-blend-saturation {
- mix-blend-mode: saturation;
-}
-.shadow {
- --tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
- --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
-}
-.shadow-lg {
- --tw-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;
- --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color),
- 0 4px 6px -4px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
-}
-.shadow-md {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
-}
-.ring {
- --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(3px + 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-4 {
- --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(4px + 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-white {
- --tw-ring-color: #fff;
-}
-.ring-offset-2 {
- --tw-ring-offset-width: 2px;
-}
-.ring-offset-blue-300 {
- --tw-ring-offset-color: #93c5fd;
-}
-.blur-md {
- --tw-blur: blur(12px);
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale)
- var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-.brightness-150 {
- --tw-brightness: brightness(1.5);
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale)
- var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-.contrast-50 {
- --tw-contrast: contrast(0.5);
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale)
- var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-.drop-shadow-md {
- --tw-drop-shadow: drop-shadow(0 4px 3px #00000012) drop-shadow(0 2px 2px #0000000f);
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale)
- var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-.grayscale {
- --tw-grayscale: grayscale(100%);
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale)
- var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-.hue-rotate-60 {
- --tw-hue-rotate: hue-rotate(60deg);
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale)
- var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-.invert {
- --tw-invert: invert(100%);
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale)
- var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-.saturate-200 {
- --tw-saturate: saturate(2);
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale)
- var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-.sepia {
- --tw-sepia: sepia(100%);
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale)
- var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-.filter {
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale)
- var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
-}
-.filter-none {
- filter: none;
-}
-.backdrop-blur-lg {
- --tw-backdrop-blur: blur(16px);
- backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
- var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
-}
-.backdrop-brightness-50 {
- --tw-backdrop-brightness: brightness(0.5);
- backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
- var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
-}
-.backdrop-contrast-0 {
- --tw-backdrop-contrast: contrast(0);
- backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
- var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
-}
-.backdrop-grayscale {
- --tw-backdrop-grayscale: grayscale(100%);
- backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
- var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
-}
-.backdrop-hue-rotate-90 {
- --tw-backdrop-hue-rotate: hue-rotate(90deg);
- backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
- var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
-}
-.backdrop-invert {
- --tw-backdrop-invert: invert(100%);
- backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
- var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
-}
-.backdrop-opacity-75 {
- --tw-backdrop-opacity: opacity(0.75);
- backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
- var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
-}
-.backdrop-saturate-150 {
- --tw-backdrop-saturate: saturate(1.5);
- backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
- var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
-}
-.backdrop-sepia {
- --tw-backdrop-sepia: sepia(100%);
- backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
- var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
-}
-.backdrop-filter {
- backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
- var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
- var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
-}
-.backdrop-filter-none {
- backdrop-filter: none;
-}
-.transition {
- transition-property: color, background-color, border-color, text-decoration-color, fill, stroke,
- opacity, box-shadow, transform, filter, backdrop-filter;
- transition-duration: 0.15s;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
-}
-.transition-all {
- transition-property: all;
- transition-duration: 0.15s;
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
-}
-.delay-300 {
- transition-delay: 0.3s;
-}
-.duration-200 {
- transition-duration: 0.2s;
-}
-.ease-in-out {
- transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
-}
diff --git a/tests/raw-content.test.css b/tests/raw-content.test.css
index dba9f3194..cc43ad4c4 100644
--- a/tests/raw-content.test.css
+++ b/tests/raw-content.test.css
@@ -27,11 +27,11 @@
}
}
.sr-only {
- width: 1px;
- height: 1px;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
+ width: 1px;
+ height: 1px;
margin: -1px;
padding: 0;
position: absolute;
@@ -265,6 +265,7 @@
cursor: pointer;
}
.select-none {
+ -webkit-user-select: none;
user-select: none;
}
.resize-none {
@@ -277,6 +278,7 @@
list-style-type: disc;
}
.appearance-none {
+ -webkit-appearance: none;
appearance: none;
}
.auto-cols-min {
@@ -701,69 +703,110 @@
}
.backdrop-blur-lg {
--tw-backdrop-blur: blur(16px);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
+ var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-brightness-50 {
--tw-backdrop-brightness: brightness(0.5);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
+ var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-contrast-0 {
--tw-backdrop-contrast: contrast(0);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
+ var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-grayscale {
--tw-backdrop-grayscale: grayscale(100%);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
+ var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-hue-rotate-90 {
--tw-backdrop-hue-rotate: hue-rotate(90deg);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
+ var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-invert {
--tw-backdrop-invert: invert(100%);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
+ var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-opacity-75 {
--tw-backdrop-opacity: opacity(0.75);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
+ var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-saturate-150 {
--tw-backdrop-saturate: saturate(1.5);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
+ var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-sepia {
--tw-backdrop-sepia: sepia(100%);
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
+ var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-filter {
+ -webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
+ var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
+ var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
+ var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.backdrop-filter-none {
+ -webkit-backdrop-filter: none;
backdrop-filter: none;
}
.transition {
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke,
- opacity, box-shadow, transform, filter, backdrop-filter;
+ opacity, box-shadow, transform, filter, -webkit-backdrop-filter, backdrop-filter;
transition-duration: 0.15s;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
diff --git a/tests/raw-content.test.js b/tests/raw-content.test.js
index 417c37485..a55e373fb 100644
--- a/tests/raw-content.test.js
+++ b/tests/raw-content.test.js
@@ -1,173 +1,164 @@
import fs from 'fs'
import path from 'path'
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(({ stable, oxide }) => {
- it('raw content', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
+it('raw content', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind components;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind components;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- stable
- .expect(result.css)
- .toMatchFormattedCss(
- fs.readFileSync(path.resolve(__dirname, './raw-content.test.css'), 'utf8')
- )
- oxide
- .expect(result.css)
- .toMatchFormattedCss(
- fs.readFileSync(path.resolve(__dirname, './raw-content.oxide.test.css'), 'utf8')
- )
- })
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(
+ fs.readFileSync(path.resolve(__dirname, './raw-content.test.css'), 'utf8')
+ )
})
})
diff --git a/tests/relative-purge-paths.test.js b/tests/relative-purge-paths.test.js
index 95fbe7bad..0e7e5feed 100644
--- a/tests/relative-purge-paths.test.js
+++ b/tests/relative-purge-paths.test.js
@@ -1,24 +1,22 @@
-import { crosscheck, run, css } from './util/run'
+import { run, css } from './util/run'
-crosscheck(() => {
- test('relative purge paths', () => {
- let config = {
- content: ['./tests/relative-purge-paths.test.html'],
- corePlugins: { preflight: false },
- }
+test('relative purge paths', () => {
+ let config = {
+ content: ['./tests/relative-purge-paths.test.html'],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toIncludeCss(css`
- .font-bold {
- font-weight: 700;
- }
- `)
- })
+ return run(input, config).then((result) => {
+ expect(result.css).toIncludeCss(css`
+ .font-bold {
+ font-weight: 700;
+ }
+ `)
})
})
diff --git a/tests/resolve-defaults-at-rules.test.js b/tests/resolve-defaults-at-rules.test.js
index 5c7e0738e..18424e712 100644
--- a/tests/resolve-defaults-at-rules.test.js
+++ b/tests/resolve-defaults-at-rules.test.js
@@ -1,774 +1,926 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(() => {
- test('basic utilities', async () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: ['transform', 'scale', 'rotate', 'skew'],
- }
+test('basic utilities', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: ['transform', 'scale', 'rotate', 'skew'],
+ }
- let input = css`
- @tailwind base;
- /* --- */
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ /* --- */
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- *,
- :before,
- :after,
- ::backdrop {
- --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;
- }
- .rotate-3 {
- --tw-rotate: 3deg;
- transform: translate(var(--tw-translate-x), 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));
- }
- .skew-y-6 {
- --tw-skew-y: 6deg;
- transform: translate(var(--tw-translate-x), 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));
- }
- .scale-x-110 {
- --tw-scale-x: 1.1;
- transform: translate(var(--tw-translate-x), 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));
- }
- `)
- })
- })
-
- test('with pseudo-class variants', async () => {
- let config = {
- content: [
- { raw: html`
` },
- ],
- 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,
- ::backdrop {
- --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;
- }
- .hover\:scale-x-110:hover {
- --tw-scale-x: 1.1;
- transform: translate(var(--tw-translate-x), 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));
- }
- .focus\:rotate-3:focus {
- --tw-rotate: 3deg;
- transform: translate(var(--tw-translate-x), 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\:focus\:skew-y-6:focus:hover {
- --tw-skew-y: 6deg;
- transform: translate(var(--tw-translate-x), 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));
- }
- `)
- })
- })
-
- test('with pseudo-element variants', async () => {
- let config = {
- content: [{ raw: html`
` }],
- 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,
- ::backdrop {
- --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;
- }
- .before\:scale-x-110:before {
- content: var(--tw-content);
- --tw-scale-x: 1.1;
- transform: translate(var(--tw-translate-x), 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));
- }
- .after\:rotate-3:after {
- content: var(--tw-content);
- --tw-rotate: 3deg;
- transform: translate(var(--tw-translate-x), 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));
- }
- `)
- })
- })
-
- test('with multi-class variants', async () => {
- let config = {
- content: [{ raw: html`
` }],
- 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,
- ::backdrop {
- --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;
- }
- .group:hover .group-hover\:scale-x-110 {
- --tw-scale-x: 1.1;
- transform: translate(var(--tw-translate-x), 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));
- }
- .peer:focus ~ .peer-focus\:rotate-3 {
- --tw-rotate: 3deg;
- transform: translate(var(--tw-translate-x), 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));
- }
- `)
- })
- })
-
- test('with multi-class pseudo-element variants', async () => {
- let config = {
- content: [
- { raw: html`
` },
- ],
- 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,
- ::backdrop {
- --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;
- }
- .group:hover .group-hover\:before\:scale-x-110:before {
- content: var(--tw-content);
- --tw-scale-x: 1.1;
- transform: translate(var(--tw-translate-x), 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));
- }
- .peer:focus ~ .peer-focus\:after\:rotate-3:after {
- content: var(--tw-content);
- --tw-rotate: 3deg;
- transform: translate(var(--tw-translate-x), 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));
- }
- `)
- })
- })
-
- test('with multi-class pseudo-element and pseudo-class variants', async () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- 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,
- ::backdrop {
- --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;
- }
- .group:hover .group-hover\:hover\:before\:scale-x-110:hover:before {
- content: var(--tw-content);
- --tw-scale-x: 1.1;
- transform: translate(var(--tw-translate-x), 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));
- }
- .peer:focus ~ .peer-focus\:focus\:after\:rotate-3:focus:after {
- content: var(--tw-content);
- --tw-rotate: 3deg;
- transform: translate(var(--tw-translate-x), 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));
- }
- `)
- })
- })
-
- test('with apply', async () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: ['transform', 'scale', 'rotate', 'skew'],
- }
-
- let input = css`
- @tailwind base;
- /* --- */
- @tailwind utilities;
-
- @layer utilities {
- .foo {
- @apply rotate-3;
- }
+ 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;
}
-
- .bar {
- @apply before:scale-110;
+ ::-webkit-backdrop {
+ --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;
}
-
- .baz::before {
- @apply rotate-45;
+ ::backdrop {
+ --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;
}
+ .rotate-3 {
+ --tw-rotate: 3deg;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ .skew-y-6 {
+ --tw-skew-y: 6deg;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ .scale-x-110 {
+ --tw-scale-x: 1.1;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ `)
+ })
+})
+test('with pseudo-class variants', async () => {
+ let config = {
+ content: [
+ { raw: html`
` },
+ ],
+ 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;
+ }
+ ::-webkit-backdrop {
+ --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;
+ }
+ ::backdrop {
+ --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;
+ }
+ .hover\:scale-x-110:hover {
+ --tw-scale-x: 1.1;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ .focus\:rotate-3:focus {
+ --tw-rotate: 3deg;
+ transform: translate(var(--tw-translate-x), 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\:focus\:skew-y-6:focus:hover {
+ --tw-skew-y: 6deg;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ `)
+ })
+})
+
+test('with pseudo-element variants', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ 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;
+ }
+ ::-webkit-backdrop {
+ --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;
+ }
+ ::backdrop {
+ --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;
+ }
+ .before\:scale-x-110:before {
+ content: var(--tw-content);
+ --tw-scale-x: 1.1;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ .after\:rotate-3:after {
+ content: var(--tw-content);
+ --tw-rotate: 3deg;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ `)
+ })
+})
+
+test('with multi-class variants', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ 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;
+ }
+ ::-webkit-backdrop {
+ --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;
+ }
+ ::backdrop {
+ --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;
+ }
+ .group:hover .group-hover\:scale-x-110 {
+ --tw-scale-x: 1.1;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ .peer:focus ~ .peer-focus\:rotate-3 {
+ --tw-rotate: 3deg;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ `)
+ })
+})
+
+test('with multi-class pseudo-element variants', async () => {
+ let config = {
+ content: [
+ { raw: html`
` },
+ ],
+ 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;
+ }
+ ::-webkit-backdrop {
+ --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;
+ }
+ ::backdrop {
+ --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;
+ }
+ .group:hover .group-hover\:before\:scale-x-110:before {
+ content: var(--tw-content);
+ --tw-scale-x: 1.1;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ .peer:focus ~ .peer-focus\:after\:rotate-3:after {
+ content: var(--tw-content);
+ --tw-rotate: 3deg;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ `)
+ })
+})
+
+test('with multi-class pseudo-element and pseudo-class variants', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ 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;
+ }
+ ::-webkit-backdrop {
+ --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;
+ }
+ ::backdrop {
+ --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;
+ }
+ .group:hover .group-hover\:hover\:before\:scale-x-110:hover:before {
+ content: var(--tw-content);
+ --tw-scale-x: 1.1;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ .peer:focus ~ .peer-focus\:focus\:after\:rotate-3:focus:after {
+ content: var(--tw-content);
+ --tw-rotate: 3deg;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ `)
+ })
+})
+
+test('with apply', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ 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 {
+ @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;
+ }
+ ::-webkit-backdrop {
+ --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;
+ }
+ ::backdrop {
+ --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;
+ }
+ .foo {
+ --tw-rotate: 3deg;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ .bar:before {
+ content: var(--tw-content);
+ --tw-scale-x: 1.1;
+ --tw-scale-y: 1.1;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ .baz:before {
+ --tw-rotate: 45deg;
+ transform: translate(var(--tw-translate-x), 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));
+ }
.whats ~ .next > span:hover {
- @apply skew-x-6;
+ --tw-skew-x: 6deg;
+ transform: translate(var(--tw-translate-x), 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));
}
-
- .media-queries {
- @apply md:rotate-45;
+ @media (min-width: 768px) {
+ .media-queries {
+ --tw-rotate: 45deg;
+ transform: translate(var(--tw-translate-x), 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));
+ }
}
-
.a,
.b,
.c {
- @apply skew-y-3;
+ --tw-skew-y: 3deg;
+ transform: translate(var(--tw-translate-x), 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));
}
-
.a,
.b {
- @apply rotate-45;
+ --tw-rotate: 45deg;
+ transform: translate(var(--tw-translate-x), 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));
}
-
- .a::before,
- .b::after {
- @apply rotate-90;
- }
-
- .recursive {
- @apply foo;
- }
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- *,
- :before,
- :after,
- ::backdrop {
- --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;
- }
- .foo {
- --tw-rotate: 3deg;
- transform: translate(var(--tw-translate-x), 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));
- }
- .bar:before {
- content: var(--tw-content);
- --tw-scale-x: 1.1;
- --tw-scale-y: 1.1;
- transform: translate(var(--tw-translate-x), 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));
- }
- .baz:before {
- --tw-rotate: 45deg;
- transform: translate(var(--tw-translate-x), 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));
- }
- .whats ~ .next > span:hover {
- --tw-skew-x: 6deg;
- transform: translate(var(--tw-translate-x), 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));
- }
- @media (min-width: 768px) {
- .media-queries {
- --tw-rotate: 45deg;
- transform: translate(var(--tw-translate-x), 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));
- }
- }
- .a,
- .b,
- .c {
- --tw-skew-y: 3deg;
- transform: translate(var(--tw-translate-x), 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));
- }
- .a,
- .b {
- --tw-rotate: 45deg;
- transform: translate(var(--tw-translate-x), 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));
- }
- .a:before,
- .b:after {
- --tw-rotate: 90deg;
- transform: translate(var(--tw-translate-x), 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));
- }
- .recursive {
- --tw-rotate: 3deg;
- transform: translate(var(--tw-translate-x), 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));
- }
- `)
- })
- })
-
- test('legacy pseudo-element syntax is supported', async () => {
- let config = {
- experimental: { optimizeUniversalDefaults: true },
- content: [{ raw: html`
` }],
- corePlugins: ['transform', 'scale', 'rotate', 'skew'],
- }
-
- let input = css`
- @tailwind base;
- /* --- */
- @tailwind utilities;
-
- .a:before {
- @apply rotate-45;
- }
-
+ .a:before,
.b:after {
- @apply rotate-3;
+ --tw-rotate: 90deg;
+ transform: translate(var(--tw-translate-x), 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));
}
-
- .c:first-line {
- @apply rotate-1;
+ .recursive {
+ --tw-rotate: 3deg;
+ transform: translate(var(--tw-translate-x), 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));
}
+ `)
+ })
+})
+test('legacy pseudo-element syntax is supported', async () => {
+ let config = {
+ experimental: { optimizeUniversalDefaults: true },
+ content: [{ raw: html`
` }],
+ corePlugins: ['transform', 'scale', 'rotate', 'skew'],
+ }
+
+ let input = css`
+ @tailwind base;
+ /* --- */
+ @tailwind utilities;
+
+ .a:before {
+ @apply rotate-45;
+ }
+
+ .b:after {
+ @apply rotate-3;
+ }
+
+ .c:first-line {
+ @apply rotate-1;
+ }
+
+ .d:first-letter {
+ @apply rotate-6;
+ }
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .a:before,
+ .b:after,
+ .c:first-line,
.d:first-letter {
- @apply rotate-6;
+ --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;
}
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .a:before,
- .b:after,
- .c:first-line,
- .d:first-letter {
- --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;
- }
- .a:before {
- --tw-rotate: 45deg;
- transform: translate(var(--tw-translate-x), 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));
- }
- .b:after {
- --tw-rotate: 3deg;
- transform: translate(var(--tw-translate-x), 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));
- }
- .c:first-line {
- --tw-rotate: 1deg;
- transform: translate(var(--tw-translate-x), 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));
- }
- .d:first-letter {
- --tw-rotate: 6deg;
- transform: translate(var(--tw-translate-x), 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));
- }
- `)
- })
+ .a:before {
+ --tw-rotate: 45deg;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ .b:after {
+ --tw-rotate: 3deg;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ .c:first-line {
+ --tw-rotate: 1deg;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ .d:first-letter {
+ --tw-rotate: 6deg;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ `)
})
+})
- test('with borders', async () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: ['borderWidth', 'borderColor', 'borderOpacity'],
- }
+test('with borders', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: ['borderWidth', 'borderColor', 'borderOpacity'],
+ }
- let input = css`
- @tailwind base;
- /* --- */
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ /* --- */
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .border {
- border-width: 1px;
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .border {
+ border-width: 1px;
+ }
+ .border-red-500 {
+ --tw-border-opacity: 1;
+ border-color: rgb(239 68 68 / var(--tw-border-opacity));
+ }
+ @media (min-width: 768px) {
+ .md\:border-2 {
+ border-width: 2px;
}
- .border-red-500 {
- --tw-border-opacity: 1;
- border-color: rgb(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: html`
` }],
- corePlugins: ['boxShadow', 'ringColor', 'ringWidth'],
- }
+test('with shadows', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: ['boxShadow', 'ringColor', 'ringWidth'],
+ }
- let input = css`
- @tailwind base;
- /* --- */
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ /* --- */
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- *,
- :before,
- :after,
- ::backdrop {
- --tw-ring-inset: ;
- --tw-ring-offset-width: 0px;
- --tw-ring-offset-color: #fff;
- --tw-ring-color: #3b82f680;
- --tw-ring-offset-shadow: 0 0 #0000;
- --tw-ring-shadow: 0 0 #0000;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- }
- .shadow {
- --tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
- --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
- 0 1px 2px -1px var(--tw-shadow-color);
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ *,
+ :before,
+ :after {
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: #3b82f680;
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ }
+ ::-webkit-backdrop {
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: #3b82f680;
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ }
+ ::backdrop {
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: #3b82f680;
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ }
+ .shadow {
+ --tw-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px -1px #0000001a;
+ --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color),
+ 0 1px 2px -1px var(--tw-shadow-color);
+ 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: #00000040;
+ }
+ @media (min-width: 768px) {
+ .md\:shadow-xl {
+ --tw-shadow: 0 20px 25px -5px #0000001a, 0 8px 10px -6px #0000001a;
+ --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color),
+ 0 8px 10px -6px var(--tw-shadow-color);
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: #00000040;
- }
- @media (min-width: 768px) {
- .md\:shadow-xl {
- --tw-shadow: 0 20px 25px -5px #0000001a, 0 8px 10px -6px #0000001a;
- --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color),
- 0 8px 10px -6px var(--tw-shadow-color);
- 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: html`
` }],
- 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,
- ::backdrop {
- --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;
- }
- `)
- })
- })
-
- test('when a utility uses defaults but they do not exist', async () => {
- let config = {
- content: [{ raw: html`
` }],
- 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: translate(var(--tw-translate-x), 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));
- }
- `)
- })
- })
-
- test('selectors are reduced to the lowest possible specificity', async () => {
- let config = {
- experimental: { optimizeUniversalDefaults: true },
- content: [{ raw: html`
` }],
- 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`
- .foo,
- [id='app'],
- [id='page'],
- [id='other'],
- [data-bar='baz'],
- article,
- [id='another']:before {
- --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);
- }
- `)
- })
- })
-
- test('No defaults without @tailwind base', () => {
- let config = {
- experimental: { optimizeUniversalDefaults: true },
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- // Optimize universal defaults doesn't work well with isolated modules
- // We require you to use @tailwind base to inject the defaults
- let input = css`
- @tailwind components;
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .scale-150 {
- --tw-scale-x: 1.5;
- --tw-scale-y: 1.5;
- transform: translate(var(--tw-translate-x), 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));
- }
- `)
- })
- })
-
- test('no defaults and apply without @tailwind base', () => {
- let config = {
- experimental: { optimizeUniversalDefaults: true },
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- // Optimize universal defaults doesn't work well with isolated modules
- // We require you to use @tailwind base to inject the defaults
- let input = css`
- @tailwind components;
- @tailwind utilities;
-
- .my-card {
- @apply scale-150;
- }
- `
-
- return run(input, config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .my-card {
- --tw-scale-x: 1.5;
- --tw-scale-y: 1.5;
- transform: translate(var(--tw-translate-x), 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));
- }
- `)
- })
+ `)
+ })
+})
+
+test('when no utilities that need the defaults are used', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ 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;
+ }
+ ::-webkit-backdrop {
+ --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;
+ }
+ ::backdrop {
+ --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;
+ }
+ `)
+ })
+})
+
+test('when a utility uses defaults but they do not exist', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ 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: translate(var(--tw-translate-x), 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));
+ }
+ `)
+ })
+})
+
+test('selectors are reduced to the lowest possible specificity', async () => {
+ let config = {
+ experimental: { optimizeUniversalDefaults: true },
+ content: [{ raw: html`
` }],
+ 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`
+ .foo,
+ [id='app'],
+ [id='page'],
+ [id='other'],
+ [data-bar='baz'],
+ article,
+ [id='another']:before {
+ --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);
+ }
+ `)
+ })
+})
+
+test('No defaults without @tailwind base', () => {
+ let config = {
+ experimental: { optimizeUniversalDefaults: true },
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ // Optimize universal defaults doesn't work well with isolated modules
+ // We require you to use @tailwind base to inject the defaults
+ let input = css`
+ @tailwind components;
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .scale-150 {
+ --tw-scale-x: 1.5;
+ --tw-scale-y: 1.5;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ `)
+ })
+})
+
+test('no defaults and apply without @tailwind base', () => {
+ let config = {
+ experimental: { optimizeUniversalDefaults: true },
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ // Optimize universal defaults doesn't work well with isolated modules
+ // We require you to use @tailwind base to inject the defaults
+ let input = css`
+ @tailwind components;
+ @tailwind utilities;
+
+ .my-card {
+ @apply scale-150;
+ }
+ `
+
+ return run(input, config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .my-card {
+ --tw-scale-x: 1.5;
+ --tw-scale-y: 1.5;
+ transform: translate(var(--tw-translate-x), 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));
+ }
+ `)
})
})
diff --git a/tests/resolveConfig.test.js b/tests/resolveConfig.test.js
index c99a986de..954c40659 100644
--- a/tests/resolveConfig.test.js
+++ b/tests/resolveConfig.test.js
@@ -1,1797 +1,1794 @@
import resolveConfig from '../src/util/resolveConfig'
import corePluginList from '../src/corePluginList'
-import { crosscheck } from './util/run'
-crosscheck(() => {
- test('prefix key overrides default prefix', () => {
- const userConfig = {
- prefix: 'tw-',
- }
+test('prefix key overrides default prefix', () => {
+ const userConfig = {
+ prefix: 'tw-',
+ }
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {
- screens: {
- mobile: '400px',
- },
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ screens: {
+ mobile: '400px',
},
- }
+ },
+ }
- const result = resolveConfig([userConfig, defaultConfig])
+ const result = resolveConfig([userConfig, defaultConfig])
- expect(result).toMatchObject({
- prefix: 'tw-',
- important: false,
- separator: ':',
- theme: {
- screens: {
- mobile: '400px',
- },
+ expect(result).toMatchObject({
+ prefix: 'tw-',
+ important: false,
+ separator: ':',
+ theme: {
+ screens: {
+ mobile: '400px',
},
- })
+ },
})
+})
- test('important key overrides default important', () => {
- const userConfig = {
- important: true,
- }
+test('important key overrides default important', () => {
+ const userConfig = {
+ important: true,
+ }
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {
- screens: {
- mobile: '400px',
- },
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ screens: {
+ mobile: '400px',
},
- }
+ },
+ }
- const result = resolveConfig([userConfig, defaultConfig])
+ const result = resolveConfig([userConfig, defaultConfig])
- expect(result).toMatchObject({
- prefix: '',
- important: true,
- separator: ':',
- theme: {
- screens: {
- mobile: '400px',
- },
+ expect(result).toMatchObject({
+ prefix: '',
+ important: true,
+ separator: ':',
+ theme: {
+ screens: {
+ mobile: '400px',
},
- })
+ },
})
+})
- test('important (selector) key overrides default important', () => {
- const userConfig = {
- important: '#app',
- }
+test('important (selector) key overrides default important', () => {
+ const userConfig = {
+ important: '#app',
+ }
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {
- screens: {
- mobile: '400px',
- },
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ screens: {
+ mobile: '400px',
},
- }
+ },
+ }
- const result = resolveConfig([userConfig, defaultConfig])
+ const result = resolveConfig([userConfig, defaultConfig])
- expect(result).toMatchObject({
- prefix: '',
- important: '#app',
- separator: ':',
- theme: {
- screens: {
- mobile: '400px',
- },
+ expect(result).toMatchObject({
+ prefix: '',
+ important: '#app',
+ separator: ':',
+ theme: {
+ screens: {
+ mobile: '400px',
},
- })
+ },
})
+})
- test('separator key overrides default separator', () => {
- const userConfig = {
- separator: '__',
- }
+test('separator key overrides default separator', () => {
+ const userConfig = {
+ separator: '__',
+ }
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {
- screens: {
- mobile: '400px',
- },
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ screens: {
+ mobile: '400px',
},
- }
+ },
+ }
- const result = resolveConfig([userConfig, defaultConfig])
+ const result = resolveConfig([userConfig, defaultConfig])
- expect(result).toMatchObject({
- prefix: '',
- important: false,
- separator: '__',
- theme: {
- screens: {
- mobile: '400px',
- },
+ expect(result).toMatchObject({
+ prefix: '',
+ important: false,
+ separator: '__',
+ theme: {
+ screens: {
+ mobile: '400px',
},
- })
+ },
})
+})
- test('theme key is merged instead of replaced', () => {
- const userConfig = {
- theme: {
- screens: {
- mobile: '400px',
- },
+test('theme key is merged instead of replaced', () => {
+ const userConfig = {
+ theme: {
+ screens: {
+ mobile: '400px',
},
- }
+ },
+ }
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- colors: {
- 'grey-darker': '#606f7b',
- 'grey-dark': '#8795a1',
- grey: '#b8c2cc',
- 'grey-light': '#dae1e7',
- 'grey-lighter': '#f1f5f8',
- },
- fonts: {
- sans: ['system-ui', 'BlinkMacSystemFont', '-apple-system', 'Roboto', 'sans-serif'],
- serif: ['Constantia', 'Lucida Bright', 'Georgia', 'serif'],
- },
- screens: {
- sm: '500px',
- md: '750px',
- lg: '1000px',
- },
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: {
+ 'grey-darker': '#606f7b',
+ 'grey-dark': '#8795a1',
+ grey: '#b8c2cc',
+ 'grey-light': '#dae1e7',
+ 'grey-lighter': '#f1f5f8',
},
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- colors: {
- 'grey-darker': '#606f7b',
- 'grey-dark': '#8795a1',
- grey: '#b8c2cc',
- 'grey-light': '#dae1e7',
- 'grey-lighter': '#f1f5f8',
- },
- fonts: {
- sans: ['system-ui', 'BlinkMacSystemFont', '-apple-system', 'Roboto', 'sans-serif'],
- serif: ['Constantia', 'Lucida Bright', 'Georgia', 'serif'],
- },
- screens: {
- mobile: '400px',
- },
+ fonts: {
+ sans: ['system-ui', 'BlinkMacSystemFont', '-apple-system', 'Roboto', 'sans-serif'],
+ serif: ['Constantia', 'Lucida Bright', 'Georgia', 'serif'],
},
- })
+ screens: {
+ sm: '500px',
+ md: '750px',
+ lg: '1000px',
+ },
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ colors: {
+ 'grey-darker': '#606f7b',
+ 'grey-dark': '#8795a1',
+ grey: '#b8c2cc',
+ 'grey-light': '#dae1e7',
+ 'grey-lighter': '#f1f5f8',
+ },
+ fonts: {
+ sans: ['system-ui', 'BlinkMacSystemFont', '-apple-system', 'Roboto', 'sans-serif'],
+ serif: ['Constantia', 'Lucida Bright', 'Georgia', 'serif'],
+ },
+ screens: {
+ mobile: '400px',
+ },
+ },
})
+})
- test('theme key is deeply merged instead of replaced', () => {
- const userConfig = {
- theme: {
- extend: {
- colors: {
- grey: {
- darker: '#606f7b',
- dark: '#8795a1',
- },
- },
- },
- },
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- colors: {
- grey: {
- grey: '#b8c2cc',
- light: '#dae1e7',
- lighter: '#f1f5f8',
- },
- },
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
+test('theme key is deeply merged instead of replaced', () => {
+ const userConfig = {
+ theme: {
+ extend: {
colors: {
grey: {
darker: '#606f7b',
dark: '#8795a1',
- grey: '#b8c2cc',
- light: '#dae1e7',
- lighter: '#f1f5f8',
},
},
},
- })
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: {
+ grey: {
+ grey: '#b8c2cc',
+ light: '#dae1e7',
+ lighter: '#f1f5f8',
+ },
+ },
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ colors: {
+ grey: {
+ darker: '#606f7b',
+ dark: '#8795a1',
+ grey: '#b8c2cc',
+ light: '#dae1e7',
+ lighter: '#f1f5f8',
+ },
+ },
+ },
})
+})
- test('missing top level keys are pulled from the default config', () => {
- const userConfig = {}
+test('missing top level keys are pulled from the default config', () => {
+ const userConfig = {}
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- colors: { green: '#00ff00' },
- screens: {
- mobile: '400px',
- },
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: { green: '#00ff00' },
+ screens: {
+ mobile: '400px',
},
- }
+ },
+ }
- const result = resolveConfig([userConfig, defaultConfig])
+ const result = resolveConfig([userConfig, defaultConfig])
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- colors: { green: '#00ff00' },
- screens: {
- mobile: '400px',
- },
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ colors: { green: '#00ff00' },
+ screens: {
+ mobile: '400px',
},
- })
+ },
})
+})
- test('functions in the default theme section are lazily evaluated', () => {
- const userConfig = {
- theme: {
- colors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
+test('functions in the default theme section are lazily evaluated', () => {
+ const userConfig = {
+ theme: {
+ colors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
},
- }
+ },
+ }
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- colors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
- },
- backgroundColors: (theme) => theme('colors'),
- textColors: (theme) => theme('colors'),
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
},
- }
+ backgroundColors: (theme) => theme('colors'),
+ textColors: (theme) => theme('colors'),
+ },
+ }
- const result = resolveConfig([userConfig, defaultConfig])
+ const result = resolveConfig([userConfig, defaultConfig])
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- colors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- backgroundColors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- textColors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ colors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
},
- })
+ backgroundColors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ textColors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ },
})
+})
- test('functions in the user theme section are lazily evaluated', () => {
- const userConfig = {
- theme: {
- colors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- backgroundColors: (theme) => ({
- ...theme('colors'),
- customBackground: '#bada55',
- }),
- textColors: (theme) => ({
- ...theme('colors'),
- customText: '#facade',
- }),
+test('functions in the user theme section are lazily evaluated', () => {
+ const userConfig = {
+ theme: {
+ colors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
},
- }
+ backgroundColors: (theme) => ({
+ ...theme('colors'),
+ customBackground: '#bada55',
+ }),
+ textColors: (theme) => ({
+ ...theme('colors'),
+ customText: '#facade',
+ }),
+ },
+ }
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- colors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
- },
- backgroundColors: ({ colors }) => colors,
- textColors: ({ colors }) => colors,
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
},
- }
+ backgroundColors: ({ colors }) => colors,
+ textColors: ({ colors }) => colors,
+ },
+ }
- const result = resolveConfig([userConfig, defaultConfig])
+ const result = resolveConfig([userConfig, defaultConfig])
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- colors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- backgroundColors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- customBackground: '#bada55',
- },
- textColors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- customText: '#facade',
- },
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ colors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
},
- })
+ backgroundColors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ customBackground: '#bada55',
+ },
+ textColors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ customText: '#facade',
+ },
+ },
})
+})
- test('theme values in the extend section extend the existing theme', () => {
- const userConfig = {
- theme: {
- extend: {
- opacity: {
- 25: '25',
- 75: '.75',
- },
- backgroundColors: {
- customBackground: '#bada55',
- },
- },
- },
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- colors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
- },
+test('theme values in the extend section extend the existing theme', () => {
+ const userConfig = {
+ theme: {
+ extend: {
opacity: {
- 0: '0',
- 50: '.5',
- 100: '1',
- },
- backgroundColors: (theme) => theme('colors'),
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- colors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
- },
- opacity: {
- 0: '0',
- 50: '.5',
- 100: '1',
25: '25',
75: '.75',
},
backgroundColors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
customBackground: '#bada55',
},
},
- })
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
+ },
+ opacity: {
+ 0: '0',
+ 50: '.5',
+ 100: '1',
+ },
+ backgroundColors: (theme) => theme('colors'),
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ colors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
+ },
+ opacity: {
+ 0: '0',
+ 50: '.5',
+ 100: '1',
+ 25: '25',
+ 75: '.75',
+ },
+ backgroundColors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
+ customBackground: '#bada55',
+ },
+ },
})
+})
- test('theme values in the extend section extend the user theme', () => {
- const userConfig = {
- theme: {
- opacity: {
- 0: '0',
- 20: '.2',
- 40: '.4',
- },
- height: (theme) => theme('width'),
- extend: {
- opacity: {
- 60: '.6',
- 80: '.8',
- 100: '1',
- },
- height: {
- customHeight: '500vh',
- },
- },
+test('theme values in the extend section extend the user theme', () => {
+ const userConfig = {
+ theme: {
+ opacity: {
+ 0: '0',
+ 20: '.2',
+ 40: '.4',
},
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
+ height: (theme) => theme('width'),
+ extend: {
opacity: {
- 0: '0',
- 50: '.5',
- 100: '1',
- },
- height: {
- 0: 0,
- full: '100%',
- },
- width: {
- 0: 0,
- 1: '.25rem',
- 2: '.5rem',
- 3: '.75rem',
- 4: '1rem',
- },
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- opacity: {
- 0: '0',
- 20: '.2',
- 40: '.4',
60: '.6',
80: '.8',
100: '1',
},
height: {
- 0: 0,
- 1: '.25rem',
- 2: '.5rem',
- 3: '.75rem',
- 4: '1rem',
customHeight: '500vh',
},
- width: {
- 0: 0,
- 1: '.25rem',
- 2: '.5rem',
- 3: '.75rem',
- 4: '1rem',
- },
},
- })
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ opacity: {
+ 0: '0',
+ 50: '.5',
+ 100: '1',
+ },
+ height: {
+ 0: 0,
+ full: '100%',
+ },
+ width: {
+ 0: 0,
+ 1: '.25rem',
+ 2: '.5rem',
+ 3: '.75rem',
+ 4: '1rem',
+ },
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ opacity: {
+ 0: '0',
+ 20: '.2',
+ 40: '.4',
+ 60: '.6',
+ 80: '.8',
+ 100: '1',
+ },
+ height: {
+ 0: 0,
+ 1: '.25rem',
+ 2: '.5rem',
+ 3: '.75rem',
+ 4: '1rem',
+ customHeight: '500vh',
+ },
+ width: {
+ 0: 0,
+ 1: '.25rem',
+ 2: '.5rem',
+ 3: '.75rem',
+ 4: '1rem',
+ },
+ },
})
+})
- test('theme values in the extend section can extend values that are depended on lazily', () => {
- const userConfig = {
- theme: {
- extend: {
- colors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- backgroundColors: {
- customBackground: '#bada55',
- },
- },
- },
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
+test('theme values in the extend section can extend values that are depended on lazily', () => {
+ const userConfig = {
+ theme: {
+ extend: {
colors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
- },
- backgroundColors: (theme) => theme('colors'),
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- colors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
red: 'red',
green: 'green',
blue: 'blue',
},
backgroundColors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
- red: 'red',
- green: 'green',
- blue: 'blue',
customBackground: '#bada55',
},
},
- })
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
+ },
+ backgroundColors: (theme) => theme('colors'),
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ colors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ backgroundColors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ customBackground: '#bada55',
+ },
+ },
})
+})
- test('theme values in the extend section are not deeply merged when they are simple arrays', () => {
- const userConfig = {
- theme: {
- extend: {
- fonts: {
- sans: ['Comic Sans'],
- serif: ['Papyrus', { fontFeatureSettings: '"cv11"' }],
- mono: [['Lobster', 'Papyrus'], { fontFeatureSettings: '"cv11"' }],
- },
- },
- },
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- fonts: {
- sans: ['system-ui', 'Helvetica Neue', 'sans-serif'],
- serif: ['Constantia', 'Georgia', 'serif'],
- mono: ['Menlo', 'Courier New', 'monospace'],
- },
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
+test('theme values in the extend section are not deeply merged when they are simple arrays', () => {
+ const userConfig = {
+ theme: {
+ extend: {
fonts: {
sans: ['Comic Sans'],
serif: ['Papyrus', { fontFeatureSettings: '"cv11"' }],
mono: [['Lobster', 'Papyrus'], { fontFeatureSettings: '"cv11"' }],
},
},
- })
- })
+ },
+ }
- test('theme values in the extend section are deeply merged, when they are arrays of objects', () => {
- const userConfig = {
- theme: {
- extend: {
- typography: {
- ArrayArray: {
- css: [{ a: { backgroundColor: 'red' } }, { a: { color: 'green' } }],
- },
- ObjectArray: {
- css: { a: { backgroundColor: 'red' } },
- },
- ArrayObject: {
- css: [{ a: { backgroundColor: 'red' } }, { a: { color: 'green' } }],
- },
- },
- },
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ fonts: {
+ sans: ['system-ui', 'Helvetica Neue', 'sans-serif'],
+ serif: ['Constantia', 'Georgia', 'serif'],
+ mono: ['Menlo', 'Courier New', 'monospace'],
},
- }
+ },
+ }
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ fonts: {
+ sans: ['Comic Sans'],
+ serif: ['Papyrus', { fontFeatureSettings: '"cv11"' }],
+ mono: [['Lobster', 'Papyrus'], { fontFeatureSettings: '"cv11"' }],
+ },
+ },
+ })
+})
+
+test('theme values in the extend section are deeply merged, when they are arrays of objects', () => {
+ const userConfig = {
+ theme: {
+ extend: {
typography: {
ArrayArray: {
- css: [{ a: { underline: 'none' } }],
+ css: [{ a: { backgroundColor: 'red' } }, { a: { color: 'green' } }],
},
ObjectArray: {
- css: [{ a: { underline: 'none' } }],
+ css: { a: { backgroundColor: 'red' } },
},
ArrayObject: {
- css: { a: { underline: 'none' } },
+ css: [{ a: { backgroundColor: 'red' } }, { a: { color: 'green' } }],
},
},
},
- }
+ },
+ }
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- typography: {
- ArrayArray: {
- css: [
- { a: { underline: 'none' } },
- { a: { backgroundColor: 'red' } },
- { a: { color: 'green' } },
- ],
- },
- ObjectArray: {
- css: [{ a: { underline: 'none' } }, { a: { backgroundColor: 'red' } }],
- },
- ArrayObject: {
- css: [
- { a: { underline: 'none' } },
- { a: { backgroundColor: 'red' } },
- { a: { color: 'green' } },
- ],
- },
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ typography: {
+ ArrayArray: {
+ css: [{ a: { underline: 'none' } }],
+ },
+ ObjectArray: {
+ css: [{ a: { underline: 'none' } }],
+ },
+ ArrayObject: {
+ css: { a: { underline: 'none' } },
},
},
- })
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ typography: {
+ ArrayArray: {
+ css: [
+ { a: { underline: 'none' } },
+ { a: { backgroundColor: 'red' } },
+ { a: { color: 'green' } },
+ ],
+ },
+ ObjectArray: {
+ css: [{ a: { underline: 'none' } }, { a: { backgroundColor: 'red' } }],
+ },
+ ArrayObject: {
+ css: [
+ { a: { underline: 'none' } },
+ { a: { backgroundColor: 'red' } },
+ { a: { color: 'green' } },
+ ],
+ },
+ },
+ },
})
+})
- test('the theme function can use a default value if the key is missing', () => {
- const userConfig = {
- theme: {
- colors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
+test('the theme function can use a default value if the key is missing', () => {
+ const userConfig = {
+ theme: {
+ colors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
},
- }
+ },
+ }
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
+ },
+ borderColor: (theme) => ({
+ default: theme('colors.gray', 'currentColor'),
+ ...theme('colors'),
+ }),
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ colors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ borderColor: {
+ default: 'currentColor',
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ },
+ })
+})
+
+test('the theme function can resolve function values', () => {
+ const userConfig = {
+ theme: {
+ textColor: (theme) => ({
+ lime: 'lime',
+ ...theme('colors'),
+ }),
+ backgroundColor: (theme) => ({
+ orange: 'orange',
+ ...theme('textColor'),
+ }),
+ borderColor: (theme) => theme('backgroundColor'),
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ colors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ textColor: {
+ lime: 'lime',
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ backgroundColor: {
+ lime: 'lime',
+ orange: 'orange',
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ borderColor: {
+ lime: 'lime',
+ orange: 'orange',
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ },
+ })
+})
+
+test('the theme function can resolve deep function values', () => {
+ const userConfig = {
+ theme: {
+ minWidth: (theme) => ({
+ '1/3': theme('width.1/3'),
+ }),
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ spacing: {
+ 0: '0',
+ },
+ width: (theme) => ({
+ ...theme('spacing'),
+ '1/3': '33.33333%',
+ }),
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ spacing: {
+ 0: '0',
+ },
+ width: {
+ 0: '0',
+ '1/3': '33.33333%',
+ },
+ minWidth: {
+ '1/3': '33.33333%',
+ },
+ },
+ })
+})
+
+test('theme values in the extend section are lazily evaluated', () => {
+ const userConfig = {
+ theme: {
+ colors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ extend: {
colors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
+ orange: 'orange',
},
borderColor: (theme) => ({
- default: theme('colors.gray', 'currentColor'),
- ...theme('colors'),
+ foo: theme('colors.orange'),
+ bar: theme('colors.red'),
}),
},
- }
+ },
+ }
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- colors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- borderColor: {
- default: 'currentColor',
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
},
- })
+ borderColor: (theme) => ({
+ default: theme('colors.yellow', 'currentColor'),
+ ...theme('colors'),
+ }),
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ colors: {
+ orange: 'orange',
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ borderColor: {
+ default: 'currentColor',
+ foo: 'orange',
+ bar: 'red',
+ orange: 'orange',
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ },
+ },
})
+})
- test('the theme function can resolve function values', () => {
- const userConfig = {
- theme: {
- textColor: (theme) => ({
- lime: 'lime',
- ...theme('colors'),
+test('lazily evaluated values have access to the config utils', () => {
+ const userConfig = {
+ theme: {
+ inset: (theme) => theme('margin'),
+ shift: (theme, { negative }) => ({
+ ...theme('spacing'),
+ ...negative(theme('spacing')),
+ }),
+ extend: {
+ nudge: (theme, { negative }) => ({
+ ...theme('spacing'),
+ ...negative(theme('spacing')),
}),
- backgroundColor: (theme) => ({
+ },
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ spacing: {
+ 1: '1px',
+ 2: '2px',
+ 3: '3px',
+ 4: '4px',
+ },
+ margin: (theme, { negative }) => ({
+ ...theme('spacing'),
+ ...negative(theme('spacing')),
+ }),
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ spacing: {
+ 1: '1px',
+ 2: '2px',
+ 3: '3px',
+ 4: '4px',
+ },
+ inset: {
+ '-1': '-1px',
+ '-2': '-2px',
+ '-3': '-3px',
+ '-4': '-4px',
+ 1: '1px',
+ 2: '2px',
+ 3: '3px',
+ 4: '4px',
+ },
+ margin: {
+ '-1': '-1px',
+ '-2': '-2px',
+ '-3': '-3px',
+ '-4': '-4px',
+ 1: '1px',
+ 2: '2px',
+ 3: '3px',
+ 4: '4px',
+ },
+ shift: {
+ '-1': '-1px',
+ '-2': '-2px',
+ '-3': '-3px',
+ '-4': '-4px',
+ 1: '1px',
+ 2: '2px',
+ 3: '3px',
+ 4: '4px',
+ },
+ nudge: {
+ '-1': '-1px',
+ '-2': '-2px',
+ '-3': '-3px',
+ '-4': '-4px',
+ 1: '1px',
+ 2: '2px',
+ 3: '3px',
+ 4: '4px',
+ },
+ },
+ })
+})
+
+test('the original theme is not mutated', () => {
+ const userConfig = {
+ theme: {
+ extend: {
+ colors: {
orange: 'orange',
- ...theme('textColor'),
+ },
+ },
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ colors: {
+ cyan: 'cyan',
+ magenta: 'magenta',
+ yellow: 'yellow',
+ },
+ },
+ }
+
+ resolveConfig([userConfig, defaultConfig])
+
+ expect(userConfig).toEqual({
+ theme: {
+ extend: {
+ colors: {
+ orange: 'orange',
+ },
+ },
+ },
+ })
+})
+
+test('custom properties are multiplied by -1 for negative values', () => {
+ const userConfig = {
+ theme: {
+ spacing: {
+ 0: 0,
+ 1: '1px',
+ 2: '2px',
+ 3: '3px',
+ 4: '4px',
+ auto: 'auto',
+ foo: 'var(--foo)',
+ bar: 'var(--bar, 500px)',
+ baz: 'calc(50% - 10px)',
+ qux: '10poops',
+ },
+ margin: (theme, { negative }) => ({
+ ...theme('spacing'),
+ ...negative(theme('spacing')),
+ }),
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {},
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result.theme.spacing).toEqual({
+ 0: 0,
+ 1: '1px',
+ 2: '2px',
+ 3: '3px',
+ 4: '4px',
+ auto: 'auto',
+ foo: 'var(--foo)',
+ bar: 'var(--bar, 500px)',
+ baz: 'calc(50% - 10px)',
+ qux: '10poops',
+ })
+ expect(result.theme.margin).toEqual({
+ 0: 0,
+ 1: '1px',
+ 2: '2px',
+ 3: '3px',
+ 4: '4px',
+ auto: 'auto',
+ foo: 'var(--foo)',
+ bar: 'var(--bar, 500px)',
+ baz: 'calc(50% - 10px)',
+ qux: '10poops',
+ '-0': '0',
+ '-1': '-1px',
+ '-2': '-2px',
+ '-3': '-3px',
+ '-4': '-4px',
+ '-foo': 'calc(var(--foo) * -1)',
+ '-bar': 'calc(var(--bar, 500px) * -1)',
+ '-baz': 'calc(calc(50% - 10px) * -1)',
+ '-qux': '-10poops',
+ })
+})
+
+test('more than two config objects can be resolved', () => {
+ const firstConfig = {
+ theme: {
+ extend: {
+ fontFamily: () => ({
+ code: ['Menlo', 'monospace'],
}),
- borderColor: (theme) => theme('backgroundColor'),
- },
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
colors: {
red: 'red',
- green: 'green',
- blue: 'blue',
- },
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- colors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- textColor: {
- lime: 'lime',
- red: 'red',
- green: 'green',
- blue: 'blue',
},
backgroundColor: {
- lime: 'lime',
+ customBackgroundOne: '#bada55',
+ },
+ textDecorationColor: {
orange: 'orange',
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- borderColor: {
- lime: 'lime',
- orange: 'orange',
- red: 'red',
- green: 'green',
- blue: 'blue',
},
},
- })
- })
+ },
+ }
- test('the theme function can resolve deep function values', () => {
- const userConfig = {
- theme: {
- minWidth: (theme) => ({
- '1/3': theme('width.1/3'),
- }),
- },
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- spacing: {
- 0: '0',
- },
- width: (theme) => ({
- ...theme('spacing'),
- '1/3': '33.33333%',
- }),
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- spacing: {
- 0: '0',
- },
- width: {
- 0: '0',
- '1/3': '33.33333%',
- },
- minWidth: {
- '1/3': '33.33333%',
- },
- },
- })
- })
-
- test('theme values in the extend section are lazily evaluated', () => {
- const userConfig = {
- theme: {
- colors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- extend: {
- colors: {
- orange: 'orange',
- },
- borderColor: (theme) => ({
- foo: theme('colors.orange'),
- bar: theme('colors.red'),
- }),
- },
- },
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- colors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
- },
- borderColor: (theme) => ({
- default: theme('colors.yellow', 'currentColor'),
- ...theme('colors'),
- }),
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- colors: {
- orange: 'orange',
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- borderColor: {
- default: 'currentColor',
- foo: 'orange',
- bar: 'red',
- orange: 'orange',
- red: 'red',
- green: 'green',
- blue: 'blue',
- },
- },
- })
- })
-
- test('lazily evaluated values have access to the config utils', () => {
- const userConfig = {
- theme: {
- inset: (theme) => theme('margin'),
- shift: (theme, { negative }) => ({
- ...theme('spacing'),
- ...negative(theme('spacing')),
- }),
- extend: {
- nudge: (theme, { negative }) => ({
- ...theme('spacing'),
- ...negative(theme('spacing')),
- }),
- },
- },
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- spacing: {
- 1: '1px',
- 2: '2px',
- 3: '3px',
- 4: '4px',
- },
- margin: (theme, { negative }) => ({
- ...theme('spacing'),
- ...negative(theme('spacing')),
- }),
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- spacing: {
- 1: '1px',
- 2: '2px',
- 3: '3px',
- 4: '4px',
- },
- inset: {
- '-1': '-1px',
- '-2': '-2px',
- '-3': '-3px',
- '-4': '-4px',
- 1: '1px',
- 2: '2px',
- 3: '3px',
- 4: '4px',
- },
- margin: {
- '-1': '-1px',
- '-2': '-2px',
- '-3': '-3px',
- '-4': '-4px',
- 1: '1px',
- 2: '2px',
- 3: '3px',
- 4: '4px',
- },
- shift: {
- '-1': '-1px',
- '-2': '-2px',
- '-3': '-3px',
- '-4': '-4px',
- 1: '1px',
- 2: '2px',
- 3: '3px',
- 4: '4px',
- },
- nudge: {
- '-1': '-1px',
- '-2': '-2px',
- '-3': '-3px',
- '-4': '-4px',
- 1: '1px',
- 2: '2px',
- 3: '3px',
- 4: '4px',
- },
- },
- })
- })
-
- test('the original theme is not mutated', () => {
- const userConfig = {
- theme: {
- extend: {
- colors: {
- orange: 'orange',
- },
- },
- },
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- colors: {
- cyan: 'cyan',
- magenta: 'magenta',
- yellow: 'yellow',
- },
- },
- }
-
- resolveConfig([userConfig, defaultConfig])
-
- expect(userConfig).toEqual({
- theme: {
- extend: {
- colors: {
- orange: 'orange',
- },
- },
- },
- })
- })
-
- test('custom properties are multiplied by -1 for negative values', () => {
- const userConfig = {
- theme: {
- spacing: {
- 0: 0,
- 1: '1px',
- 2: '2px',
- 3: '3px',
- 4: '4px',
- auto: 'auto',
- foo: 'var(--foo)',
- bar: 'var(--bar, 500px)',
- baz: 'calc(50% - 10px)',
- qux: '10poops',
- },
- margin: (theme, { negative }) => ({
- ...theme('spacing'),
- ...negative(theme('spacing')),
- }),
- },
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {},
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result.theme.spacing).toEqual({
- 0: 0,
- 1: '1px',
- 2: '2px',
- 3: '3px',
- 4: '4px',
- auto: 'auto',
- foo: 'var(--foo)',
- bar: 'var(--bar, 500px)',
- baz: 'calc(50% - 10px)',
- qux: '10poops',
- })
- expect(result.theme.margin).toEqual({
- 0: 0,
- 1: '1px',
- 2: '2px',
- 3: '3px',
- 4: '4px',
- auto: 'auto',
- foo: 'var(--foo)',
- bar: 'var(--bar, 500px)',
- baz: 'calc(50% - 10px)',
- qux: '10poops',
- '-0': '0',
- '-1': '-1px',
- '-2': '-2px',
- '-3': '-3px',
- '-4': '-4px',
- '-foo': 'calc(var(--foo) * -1)',
- '-bar': 'calc(var(--bar, 500px) * -1)',
- '-baz': 'calc(calc(50% - 10px) * -1)',
- '-qux': '-10poops',
- })
- })
-
- test('more than two config objects can be resolved', () => {
- const firstConfig = {
- theme: {
- extend: {
- fontFamily: () => ({
- code: ['Menlo', 'monospace'],
- }),
- colors: {
- red: 'red',
- },
- backgroundColor: {
- customBackgroundOne: '#bada55',
- },
- textDecorationColor: {
- orange: 'orange',
- },
- },
- },
- }
-
- const secondConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- extend: {
- fontFamily: {
- quote: ['Helvetica', 'serif'],
- },
- colors: {
- green: 'green',
- },
- backgroundColor: {
- customBackgroundTwo: '#facade',
- },
- textDecorationColor: (theme) => theme('colors'),
- },
- },
- }
-
- const thirdConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- extend: {
- fontFamily: {
- hero: ['Futura', 'sans-serif'],
- },
- colors: {
- pink: 'pink',
- },
- backgroundColor: () => ({
- customBackgroundThree: '#c0ffee',
- }),
- textDecorationColor: {
- lime: 'lime',
- },
- },
- },
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
+ const secondConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ extend: {
fontFamily: {
- body: ['Arial', 'sans-serif'],
- display: ['Georgia', 'serif'],
- },
- colors: {
- blue: 'blue',
- },
- backgroundColor: (theme) => theme('colors'),
- },
- }
-
- const result = resolveConfig([firstConfig, secondConfig, thirdConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- fontFamily: {
- body: ['Arial', 'sans-serif'],
- display: ['Georgia', 'serif'],
- code: ['Menlo', 'monospace'],
quote: ['Helvetica', 'serif'],
+ },
+ colors: {
+ green: 'green',
+ },
+ backgroundColor: {
+ customBackgroundTwo: '#facade',
+ },
+ textDecorationColor: (theme) => theme('colors'),
+ },
+ },
+ }
+
+ const thirdConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ extend: {
+ fontFamily: {
hero: ['Futura', 'sans-serif'],
},
colors: {
- red: 'red',
- green: 'green',
- blue: 'blue',
pink: 'pink',
},
- backgroundColor: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- pink: 'pink',
- customBackgroundOne: '#bada55',
- customBackgroundTwo: '#facade',
+ backgroundColor: () => ({
customBackgroundThree: '#c0ffee',
- },
+ }),
textDecorationColor: {
- red: 'red',
- green: 'green',
- blue: 'blue',
- pink: 'pink',
- orange: 'orange',
lime: 'lime',
},
},
- })
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ fontFamily: {
+ body: ['Arial', 'sans-serif'],
+ display: ['Georgia', 'serif'],
+ },
+ colors: {
+ blue: 'blue',
+ },
+ backgroundColor: (theme) => theme('colors'),
+ },
+ }
+
+ const result = resolveConfig([firstConfig, secondConfig, thirdConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ fontFamily: {
+ body: ['Arial', 'sans-serif'],
+ display: ['Georgia', 'serif'],
+ code: ['Menlo', 'monospace'],
+ quote: ['Helvetica', 'serif'],
+ hero: ['Futura', 'sans-serif'],
+ },
+ colors: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ pink: 'pink',
+ },
+ backgroundColor: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ pink: 'pink',
+ customBackgroundOne: '#bada55',
+ customBackgroundTwo: '#facade',
+ customBackgroundThree: '#c0ffee',
+ },
+ textDecorationColor: {
+ red: 'red',
+ green: 'green',
+ blue: 'blue',
+ pink: 'pink',
+ orange: 'orange',
+ lime: 'lime',
+ },
+ },
})
+})
- test('plugin config modifications are applied', () => {
- const userConfig = {
- plugins: [
- {
- config: {
- prefix: 'tw-',
- },
- },
- ],
- }
-
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {
- screens: {
- mobile: '400px',
+test('plugin config modifications are applied', () => {
+ const userConfig = {
+ plugins: [
+ {
+ config: {
+ prefix: 'tw-',
},
},
- }
+ ],
+ }
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: 'tw-',
- important: false,
- separator: ':',
- theme: {
- screens: {
- mobile: '400px',
- },
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ screens: {
+ mobile: '400px',
},
- plugins: userConfig.plugins,
- })
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: 'tw-',
+ important: false,
+ separator: ':',
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
+ },
+ plugins: userConfig.plugins,
})
+})
- test('user config takes precedence over plugin config modifications', () => {
- const userConfig = {
- prefix: 'user-',
- plugins: [
- {
- config: {
- prefix: 'tw-',
- },
- },
- ],
- }
-
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {
- screens: {
- mobile: '400px',
+test('user config takes precedence over plugin config modifications', () => {
+ const userConfig = {
+ prefix: 'user-',
+ plugins: [
+ {
+ config: {
+ prefix: 'tw-',
},
},
- }
+ ],
+ }
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: 'user-',
- important: false,
- separator: ':',
- theme: {
- screens: {
- mobile: '400px',
- },
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ screens: {
+ mobile: '400px',
},
- plugins: userConfig.plugins,
- })
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: 'user-',
+ important: false,
+ separator: ':',
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
+ },
+ plugins: userConfig.plugins,
})
+})
- test('plugin config can register plugins that also have config', () => {
- const userConfig = {
- plugins: [
- {
- config: {
- prefix: 'tw-',
- plugins: [
- {
- config: {
- important: true,
- },
+test('plugin config can register plugins that also have config', () => {
+ const userConfig = {
+ plugins: [
+ {
+ config: {
+ prefix: 'tw-',
+ plugins: [
+ {
+ config: {
+ important: true,
},
- {
- config: {
- separator: '__',
- },
+ },
+ {
+ config: {
+ separator: '__',
},
- ],
- },
- handler() {},
- },
- ],
- }
-
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {
- screens: {
- mobile: '400px',
+ },
+ ],
},
+ handler() {},
},
- }
+ ],
+ }
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: 'tw-',
- important: true,
- separator: '__',
- theme: {
- screens: {
- mobile: '400px',
- },
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ screens: {
+ mobile: '400px',
},
- plugins: userConfig.plugins,
- })
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: 'tw-',
+ important: true,
+ separator: '__',
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
+ },
+ plugins: userConfig.plugins,
})
+})
- test('plugin configs take precedence over plugin configs registered by that plugin', () => {
- const userConfig = {
- plugins: [
- {
- config: {
- prefix: 'outer-',
- plugins: [
- {
- config: {
- prefix: 'inner-',
- },
+test('plugin configs take precedence over plugin configs registered by that plugin', () => {
+ const userConfig = {
+ plugins: [
+ {
+ config: {
+ prefix: 'outer-',
+ plugins: [
+ {
+ config: {
+ prefix: 'inner-',
},
- ],
- },
- handler() {},
- },
- ],
- }
-
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {
- screens: {
- mobile: '400px',
+ },
+ ],
},
+ handler() {},
},
- }
+ ],
+ }
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: 'outer-',
- important: false,
- separator: ':',
- theme: {
- screens: {
- mobile: '400px',
- },
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ screens: {
+ mobile: '400px',
},
- plugins: userConfig.plugins,
- })
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: 'outer-',
+ important: false,
+ separator: ':',
+ theme: {
+ screens: {
+ mobile: '400px',
+ },
+ },
+ plugins: userConfig.plugins,
})
+})
- test('plugin theme extensions are added even if user overrides top-level theme config', () => {
- const userConfig = {
- theme: {
- width: {
- '1px': '1px',
- },
+test('plugin theme extensions are added even if user overrides top-level theme config', () => {
+ const userConfig = {
+ theme: {
+ width: {
+ '1px': '1px',
},
- plugins: [
- {
- config: {
- theme: {
- extend: {
- width: {
- '2px': '2px',
- '3px': '3px',
- },
+ },
+ plugins: [
+ {
+ config: {
+ theme: {
+ extend: {
+ width: {
+ '2px': '2px',
+ '3px': '3px',
},
},
},
- handler() {},
- },
- ],
- }
-
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {
- width: {
- sm: '1rem',
- md: '2rem',
- lg: '3rem',
- },
- screens: {
- mobile: '400px',
},
+ handler() {},
},
- }
+ ],
+ }
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '',
- important: false,
- separator: ':',
- theme: {
- width: {
- '1px': '1px',
- '2px': '2px',
- '3px': '3px',
- },
- screens: {
- mobile: '400px',
- },
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ width: {
+ sm: '1rem',
+ md: '2rem',
+ lg: '3rem',
},
- plugins: userConfig.plugins,
- })
+ screens: {
+ mobile: '400px',
+ },
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '',
+ important: false,
+ separator: ':',
+ theme: {
+ width: {
+ '1px': '1px',
+ '2px': '2px',
+ '3px': '3px',
+ },
+ screens: {
+ mobile: '400px',
+ },
+ },
+ plugins: userConfig.plugins,
})
+})
- test('user theme extensions take precedence over plugin theme extensions with the same key', () => {
- const userConfig = {
- theme: {
- extend: {
- width: {
- xl: '6rem',
- },
- },
- },
- plugins: [
- {
- config: {
- theme: {
- extend: {
- width: {
- xl: '4rem',
- },
- },
- },
- },
- handler() {},
- },
- ],
- }
-
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {
+test('user theme extensions take precedence over plugin theme extensions with the same key', () => {
+ const userConfig = {
+ theme: {
+ extend: {
width: {
- sm: '1rem',
- md: '2rem',
- lg: '3rem',
- },
- screens: {
- mobile: '400px',
- },
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '',
- important: false,
- separator: ':',
- theme: {
- width: {
- sm: '1rem',
- md: '2rem',
- lg: '3rem',
xl: '6rem',
},
- screens: {
- mobile: '400px',
- },
},
- plugins: userConfig.plugins,
- })
+ },
+ plugins: [
+ {
+ config: {
+ theme: {
+ extend: {
+ width: {
+ xl: '4rem',
+ },
+ },
+ },
+ },
+ handler() {},
+ },
+ ],
+ }
+
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ width: {
+ sm: '1rem',
+ md: '2rem',
+ lg: '3rem',
+ },
+ screens: {
+ mobile: '400px',
+ },
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '',
+ important: false,
+ separator: ':',
+ theme: {
+ width: {
+ sm: '1rem',
+ md: '2rem',
+ lg: '3rem',
+ xl: '6rem',
+ },
+ screens: {
+ mobile: '400px',
+ },
+ },
+ plugins: userConfig.plugins,
})
+})
- test('extensions are applied in the right order', () => {
- const userConfig = {
- theme: {
- extend: {
- colors: {
- grey: {
- light: '#eee',
- },
- },
- },
- },
- }
-
- const otherConfig = {
- theme: {
- extend: {
- colors: {
- grey: {
- light: '#ddd',
- darker: '#111',
- },
- },
- },
- },
- }
-
- const anotherConfig = {
- theme: {
- extend: {
- colors: {
- grey: {
- darker: '#222',
- },
- },
- },
- },
- }
-
- const defaultConfig = {
- content: [],
- theme: {
- colors: {
- grey: {
- light: '#ccc',
- dark: '#333',
- },
- },
- },
- }
-
- const result = resolveConfig([userConfig, otherConfig, anotherConfig, defaultConfig])
-
- expect(result).toMatchObject({
- theme: {
+test('extensions are applied in the right order', () => {
+ const userConfig = {
+ theme: {
+ extend: {
colors: {
grey: {
light: '#eee',
- dark: '#333',
+ },
+ },
+ },
+ },
+ }
+
+ const otherConfig = {
+ theme: {
+ extend: {
+ colors: {
+ grey: {
+ light: '#ddd',
darker: '#111',
},
},
},
- })
- })
+ },
+ }
- test('core plugin configuration builds on the default list when starting with an empty object', () => {
- const userConfig = {
- corePlugins: { display: false },
- }
-
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {},
- corePlugins: {},
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '',
- important: false,
- separator: ':',
- theme: {},
- corePlugins: corePluginList.filter((c) => c !== 'display'),
- })
- })
-
- test('core plugins that are disabled by default can be enabled', () => {
- const userConfig = {
- corePlugins: { display: true },
- }
-
- const defaultConfig = {
- presets: [],
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {},
- corePlugins: { display: false },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
- expect(result.corePlugins).toContain('display')
- })
-
- test('core plugin configurations stack', () => {
- const userConfig = {
- corePlugins: { display: false },
- }
-
- const otherConfig = {
- corePlugins: ({ corePlugins }) => {
- return [...corePlugins, 'margin']
- },
- }
-
- const defaultConfig = {
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {},
- corePlugins: ['float', 'display', 'padding'],
- }
-
- const result = resolveConfig([userConfig, otherConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '',
- important: false,
- separator: ':',
- theme: {},
- corePlugins: ['float', 'padding', 'margin'],
- })
- })
-
- test('plugins are merged', () => {
- const userConfig = {
- plugins: ['3'],
- }
-
- const otherConfig = {
- plugins: ['2'],
- }
-
- const defaultConfig = {
- plugins: ['1'],
- prefix: '',
- important: false,
- separator: ':',
- content: [],
- theme: {},
- }
-
- const result = resolveConfig([userConfig, otherConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '',
- important: false,
- separator: ':',
- theme: {},
- plugins: ['1', '2', '3'],
- })
- })
-
- test('all helpers can be destructured from the first function argument', () => {
- const userConfig = {
- theme: {
- example: ({ theme, colors, negative, breakpoints }) => ({
- weight: theme('fontWeight.bold'),
- black: colors.black,
- white: colors.white,
- ...negative(theme('spacing')),
- ...breakpoints(theme('screens')),
- }),
- },
- }
-
- const defaultConfig = {
- prefix: '-',
- important: false,
- separator: ':',
- content: [],
- theme: {
- screens: {
- sm: '640px',
- md: '768px',
- },
- fontWeight: {
- bold: 700,
- },
- spacing: {
- 0: '0px',
- 1: '1px',
- 2: '2px',
- 3: '3px',
- 4: '4px',
- },
- },
- }
-
- const result = resolveConfig([userConfig, defaultConfig])
-
- expect(result).toMatchObject({
- prefix: '-',
- important: false,
- separator: ':',
- theme: {
- example: {
- weight: 700,
- black: '#000',
- white: '#fff',
- '-1': '-1px',
- '-2': '-2px',
- '-3': '-3px',
- '-4': '-4px',
- 'screen-sm': '640px',
- 'screen-md': '768px',
- },
- },
- })
- })
-
- test('does not duplicate extended configs every time resolveConfig is called', () => {
- let shared = {
- foo: { bar: { baz: [{ color: 'red' }] } },
- }
-
- const createConfig = (color) =>
- resolveConfig([
- {
- theme: {
- foo: shared.foo,
- extend: {
- foo: { bar: { baz: { color } } },
- },
+ const anotherConfig = {
+ theme: {
+ extend: {
+ colors: {
+ grey: {
+ darker: '#222',
},
},
- ])
+ },
+ },
+ }
- createConfig('orange')
- createConfig('yellow')
- createConfig('green')
+ const defaultConfig = {
+ content: [],
+ theme: {
+ colors: {
+ grey: {
+ light: '#ccc',
+ dark: '#333',
+ },
+ },
+ },
+ }
- const result = createConfig('blue')
+ const result = resolveConfig([userConfig, otherConfig, anotherConfig, defaultConfig])
- expect(shared.foo.bar.baz).toMatchObject([{ color: 'red' }])
- expect(result.theme.foo.bar.baz).toMatchObject([{ color: 'red' }, { color: 'blue' }])
+ expect(result).toMatchObject({
+ theme: {
+ colors: {
+ grey: {
+ light: '#eee',
+ dark: '#333',
+ darker: '#111',
+ },
+ },
+ },
})
})
+
+test('core plugin configuration builds on the default list when starting with an empty object', () => {
+ const userConfig = {
+ corePlugins: { display: false },
+ }
+
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {},
+ corePlugins: {},
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '',
+ important: false,
+ separator: ':',
+ theme: {},
+ corePlugins: corePluginList.filter((c) => c !== 'display'),
+ })
+})
+
+test('core plugins that are disabled by default can be enabled', () => {
+ const userConfig = {
+ corePlugins: { display: true },
+ }
+
+ const defaultConfig = {
+ presets: [],
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {},
+ corePlugins: { display: false },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+ expect(result.corePlugins).toContain('display')
+})
+
+test('core plugin configurations stack', () => {
+ const userConfig = {
+ corePlugins: { display: false },
+ }
+
+ const otherConfig = {
+ corePlugins: ({ corePlugins }) => {
+ return [...corePlugins, 'margin']
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {},
+ corePlugins: ['float', 'display', 'padding'],
+ }
+
+ const result = resolveConfig([userConfig, otherConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '',
+ important: false,
+ separator: ':',
+ theme: {},
+ corePlugins: ['float', 'padding', 'margin'],
+ })
+})
+
+test('plugins are merged', () => {
+ const userConfig = {
+ plugins: ['3'],
+ }
+
+ const otherConfig = {
+ plugins: ['2'],
+ }
+
+ const defaultConfig = {
+ plugins: ['1'],
+ prefix: '',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {},
+ }
+
+ const result = resolveConfig([userConfig, otherConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '',
+ important: false,
+ separator: ':',
+ theme: {},
+ plugins: ['1', '2', '3'],
+ })
+})
+
+test('all helpers can be destructured from the first function argument', () => {
+ const userConfig = {
+ theme: {
+ example: ({ theme, colors, negative, breakpoints }) => ({
+ weight: theme('fontWeight.bold'),
+ black: colors.black,
+ white: colors.white,
+ ...negative(theme('spacing')),
+ ...breakpoints(theme('screens')),
+ }),
+ },
+ }
+
+ const defaultConfig = {
+ prefix: '-',
+ important: false,
+ separator: ':',
+ content: [],
+ theme: {
+ screens: {
+ sm: '640px',
+ md: '768px',
+ },
+ fontWeight: {
+ bold: 700,
+ },
+ spacing: {
+ 0: '0px',
+ 1: '1px',
+ 2: '2px',
+ 3: '3px',
+ 4: '4px',
+ },
+ },
+ }
+
+ const result = resolveConfig([userConfig, defaultConfig])
+
+ expect(result).toMatchObject({
+ prefix: '-',
+ important: false,
+ separator: ':',
+ theme: {
+ example: {
+ weight: 700,
+ black: '#000',
+ white: '#fff',
+ '-1': '-1px',
+ '-2': '-2px',
+ '-3': '-3px',
+ '-4': '-4px',
+ 'screen-sm': '640px',
+ 'screen-md': '768px',
+ },
+ },
+ })
+})
+
+test('does not duplicate extended configs every time resolveConfig is called', () => {
+ let shared = {
+ foo: { bar: { baz: [{ color: 'red' }] } },
+ }
+
+ const createConfig = (color) =>
+ resolveConfig([
+ {
+ theme: {
+ foo: shared.foo,
+ extend: {
+ foo: { bar: { baz: { color } } },
+ },
+ },
+ },
+ ])
+
+ createConfig('orange')
+ createConfig('yellow')
+ createConfig('green')
+
+ const result = createConfig('blue')
+
+ expect(shared.foo.bar.baz).toMatchObject([{ color: 'red' }])
+ expect(result.theme.foo.bar.baz).toMatchObject([{ color: 'red' }, { color: 'blue' }])
+})
diff --git a/tests/responsive-and-variants-atrules.test.js b/tests/responsive-and-variants-atrules.test.js
index 9d9ed5d85..4d4a0f507 100644
--- a/tests/responsive-and-variants-atrules.test.js
+++ b/tests/responsive-and-variants-atrules.test.js
@@ -1,157 +1,155 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(() => {
- test('responsive and variants atrules', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
+test('responsive and variants atrules', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind components;
- @tailwind utilities;
-
- @layer utilities {
- @responsive {
- .responsive-in-utilities {
- color: blue;
- }
- }
- @variants {
- .variants-in-utilities {
- color: red;
- }
- }
- @responsive {
- @variants {
- .both-in-utilities {
- color: green;
- }
- }
- }
- }
+ let input = css`
+ @tailwind components;
+ @tailwind utilities;
+ @layer utilities {
@responsive {
- .responsive-at-root {
- color: white;
+ .responsive-in-utilities {
+ color: blue;
}
}
@variants {
- .variants-at-root {
- color: orange;
+ .variants-in-utilities {
+ color: red;
}
}
@responsive {
@variants {
- .both-at-root {
- color: pink;
+ .both-in-utilities {
+ color: green;
}
}
}
+ }
- @layer components {
- @responsive {
- .responsive-in-components {
- color: blue;
- }
- }
- @variants {
- .variants-in-components {
- color: red;
- }
- }
- @responsive {
- @variants {
- .both-in-components {
- color: green;
- }
- }
- }
+ @responsive {
+ .responsive-at-root {
+ color: white;
}
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .responsive-in-components {
- color: #00f;
- }
- .variants-in-components {
- color: red;
- }
- .both-in-components {
- color: green;
- }
- .responsive-in-utilities {
- color: #00f;
- }
- .variants-in-utilities {
- color: red;
- }
- .both-in-utilities {
- color: green;
- }
- .responsive-at-root {
- color: #fff;
- }
- .variants-at-root {
- color: orange;
- }
+ }
+ @variants {
+ .variants-at-root {
+ color: orange;
+ }
+ }
+ @responsive {
+ @variants {
.both-at-root {
color: pink;
}
- @media (min-width: 768px) {
- .md\:focus\:responsive-in-components:focus {
- color: #00f;
- }
- .md\:focus\:variants-in-components:focus {
- color: red;
- }
- .md\:focus\:both-in-components:focus {
+ }
+ }
+
+ @layer components {
+ @responsive {
+ .responsive-in-components {
+ color: blue;
+ }
+ }
+ @variants {
+ .variants-in-components {
+ color: red;
+ }
+ }
+ @responsive {
+ @variants {
+ .both-in-components {
color: green;
}
- .md\:focus\:responsive-in-utilities:focus {
- color: #00f;
- }
- .md\:focus\:variants-in-utilities:focus {
- color: red;
- }
- .md\:focus\:both-in-utilities:focus {
- color: green;
- }
- .md\:focus\:responsive-at-root:focus {
- color: #fff;
- }
- .md\:focus\:variants-at-root:focus {
- color: orange;
- }
- .md\:focus\:both-at-root:focus {
- color: pink;
- }
}
- `)
- })
+ }
+ }
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .responsive-in-components {
+ color: #00f;
+ }
+ .variants-in-components {
+ color: red;
+ }
+ .both-in-components {
+ color: green;
+ }
+ .responsive-in-utilities {
+ color: #00f;
+ }
+ .variants-in-utilities {
+ color: red;
+ }
+ .both-in-utilities {
+ color: green;
+ }
+ .responsive-at-root {
+ color: #fff;
+ }
+ .variants-at-root {
+ color: orange;
+ }
+ .both-at-root {
+ color: pink;
+ }
+ @media (min-width: 768px) {
+ .md\:focus\:responsive-in-components:focus {
+ color: #00f;
+ }
+ .md\:focus\:variants-in-components:focus {
+ color: red;
+ }
+ .md\:focus\:both-in-components:focus {
+ color: green;
+ }
+ .md\:focus\:responsive-in-utilities:focus {
+ color: #00f;
+ }
+ .md\:focus\:variants-in-utilities:focus {
+ color: red;
+ }
+ .md\:focus\:both-in-utilities:focus {
+ color: green;
+ }
+ .md\:focus\:responsive-at-root:focus {
+ color: #fff;
+ }
+ .md\:focus\:variants-at-root:focus {
+ color: orange;
+ }
+ .md\:focus\:both-at-root:focus {
+ color: pink;
+ }
+ }
+ `)
})
})
diff --git a/tests/safelist.test.js b/tests/safelist.test.js
index cbbd1e245..e2621f846 100644
--- a/tests/safelist.test.js
+++ b/tests/safelist.test.js
@@ -1,636 +1,491 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(({ stable, oxide }) => {
- it('should not safelist anything', () => {
- let config = {
- content: [{ raw: html`
` }],
- }
+it('should not safelist anything', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ }
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .uppercase {
- text-transform: uppercase;
- }
- `)
- })
- })
-
- it('should safelist strings', () => {
- let config = {
- content: [{ raw: html`
` }],
- safelist: ['mt-[20px]', 'font-bold', 'text-gray-200', 'hover:underline'],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .mt-\[20px\] {
- margin-top: 20px;
- }
- .font-bold {
- font-weight: 700;
- }
- .uppercase {
- text-transform: uppercase;
- }
- .text-gray-200 {
- --tw-text-opacity: 1;
- color: rgb(229 231 235 / var(--tw-text-opacity));
- }
- .hover\:underline:hover {
- text-decoration-line: underline;
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .mt-\[20px\] {
- margin-top: 20px;
- }
- .font-bold {
- font-weight: 700;
- }
- .uppercase {
- text-transform: uppercase;
- }
- .text-gray-200 {
- color: #e5e7eb;
- }
- .hover\:underline:hover {
- text-decoration-line: underline;
- }
- `)
- })
- })
-
- it('should safelist based on a pattern regex', () => {
- let config = {
- content: [{ raw: html`
` }],
- safelist: [
- {
- pattern: /^bg-(red)-(100|200)$/,
- variants: ['hover'],
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .bg-red-100 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 226 226 / var(--tw-bg-opacity));
- }
- .bg-red-200 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 202 202 / var(--tw-bg-opacity));
- }
- .uppercase {
- text-transform: uppercase;
- }
- .hover\:bg-red-100:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(254 226 226 / var(--tw-bg-opacity));
- }
- .hover\:bg-red-200:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(254 202 202 / var(--tw-bg-opacity));
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .bg-red-100 {
- background-color: #fee2e2;
- }
- .bg-red-200 {
- background-color: #fecaca;
- }
- .uppercase {
- text-transform: uppercase;
- }
- .hover\:bg-red-100:hover {
- background-color: #fee2e2;
- }
- .hover\:bg-red-200:hover {
- background-color: #fecaca;
- }
- `)
- })
- })
-
- it('should not generate duplicates', () => {
- let config = {
- content: [{ raw: html`
` }],
- safelist: [
- 'uppercase',
- {
- pattern: /^bg-(red)-(100|200)$/,
- variants: ['hover'],
- },
- {
- pattern: /^bg-(red)-(100|200)$/,
- variants: ['hover'],
- },
- {
- pattern: /^bg-(red)-(100|200)$/,
- variants: ['hover'],
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .bg-red-100 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 226 226 / var(--tw-bg-opacity));
- }
- .bg-red-200 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 202 202 / var(--tw-bg-opacity));
- }
- .uppercase {
- text-transform: uppercase;
- }
- .hover\:bg-red-100:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(254 226 226 / var(--tw-bg-opacity));
- }
- .hover\:bg-red-200:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(254 202 202 / var(--tw-bg-opacity));
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .bg-red-100 {
- background-color: #fee2e2;
- }
- .bg-red-200 {
- background-color: #fecaca;
- }
- .uppercase {
- text-transform: uppercase;
- }
- .hover\:bg-red-100:hover {
- background-color: #fee2e2;
- }
- .hover\:bg-red-200:hover {
- background-color: #fecaca;
- }
- `)
- })
- })
-
- it('should safelist when using a custom prefix', () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`
` }],
- safelist: [
- {
- pattern: /^tw-bg-red-(100|200)$/g,
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .tw-bg-red-100 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 226 226 / var(--tw-bg-opacity));
- }
- .tw-bg-red-200 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 202 202 / var(--tw-bg-opacity));
- }
- .tw-uppercase {
- text-transform: uppercase;
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .tw-bg-red-100 {
- background-color: #fee2e2;
- }
- .tw-bg-red-200 {
- background-color: #fecaca;
- }
- .tw-uppercase {
- text-transform: uppercase;
- }
- `)
- })
- })
-
- it('should not safelist when an empty list is provided', () => {
- let config = {
- content: [{ raw: html`
` }],
- safelist: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .uppercase {
- text-transform: uppercase;
- }
- `)
- })
- })
-
- it('should not safelist when an sparse/holey list is provided', () => {
- let config = {
- content: [{ raw: html`
` }],
- safelist: [, , ,],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .uppercase {
- text-transform: uppercase;
- }
- `)
- })
- })
-
- it('should not safelist any invalid variants if provided', () => {
- let config = {
- content: [{ raw: html`
` }],
- safelist: [
- {
- pattern: /^bg-(red)-(100|200)$/,
- variants: ['foo', 'bar'],
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .bg-red-100 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 226 226 / var(--tw-bg-opacity));
- }
- .bg-red-200 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 202 202 / var(--tw-bg-opacity));
- }
- .uppercase {
- text-transform: uppercase;
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .bg-red-100 {
- background-color: #fee2e2;
- }
- .bg-red-200 {
- background-color: #fecaca;
- }
- .uppercase {
- text-transform: uppercase;
- }
- `)
- })
- })
-
- it('should safelist negatives based on a pattern regex', () => {
- let config = {
- content: [{ raw: html`
` }],
- safelist: [
- {
- pattern: /^-top-1$/,
- variants: ['hover'],
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .-top-1 {
- top: -0.25rem;
- }
- .uppercase {
- text-transform: uppercase;
- }
- .hover\:-top-1:hover {
- top: -0.25rem;
- }
- `)
- })
- })
-
- it('should safelist negatives based on a pattern regex', () => {
- let config = {
- content: [{ raw: html`
` }],
- safelist: [
- {
- pattern: /^bg-red-(400|500)(\/(40|50))?$/,
- variants: ['hover'],
- },
- {
- pattern: /^(fill|ring|text)-red-200\/50$/,
- variants: ['hover'],
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .bg-red-400 {
- --tw-bg-opacity: 1;
- background-color: rgb(248 113 113 / var(--tw-bg-opacity));
- }
- .bg-red-400\/40 {
- background-color: #f8717166;
- }
- .bg-red-400\/50 {
- background-color: #f8717180;
- }
- .bg-red-500 {
- --tw-bg-opacity: 1;
- background-color: rgb(239 68 68 / var(--tw-bg-opacity));
- }
- .bg-red-500\/40 {
- background-color: #ef444466;
- }
- .bg-red-500\/50 {
- background-color: #ef444480;
- }
- .fill-red-200\/50 {
- fill: #fecaca80;
- }
- .uppercase {
- text-transform: uppercase;
- }
- .text-red-200\/50 {
- color: #fecaca80;
- }
- .ring-red-200\/50 {
- --tw-ring-color: #fecaca80;
- }
- .hover\:bg-red-400:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(248 113 113 / var(--tw-bg-opacity));
- }
- .hover\:bg-red-400\/40:hover {
- background-color: #f8717166;
- }
- .hover\:bg-red-400\/50:hover {
- background-color: #f8717180;
- }
- .hover\:bg-red-500:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(239 68 68 / var(--tw-bg-opacity));
- }
- .hover\:bg-red-500\/40:hover {
- background-color: #ef444466;
- }
- .hover\:bg-red-500\/50:hover {
- background-color: #ef444480;
- }
- .hover\:fill-red-200\/50:hover {
- fill: #fecaca80;
- }
- .hover\:text-red-200\/50:hover {
- color: #fecaca80;
- }
- .hover\:ring-red-200\/50:hover {
- --tw-ring-color: #fecaca80;
- }
- `)
- })
- })
-
- it('should safelist pattern regex with !important selector', () => {
- let config = {
- content: [{ raw: html`
` }],
- safelist: [{ pattern: /^!grid-cols-(4|5|6)$/ }],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .\!grid-cols-4 {
- grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
- }
- .\!grid-cols-5 {
- grid-template-columns: repeat(5, minmax(0, 1fr)) !important;
- }
- .\!grid-cols-6 {
- grid-template-columns: repeat(6, minmax(0, 1fr)) !important;
- }
- .uppercase {
- text-transform: uppercase;
- }
- `)
- })
- })
-
- it('should safelist pattern regex with custom prefix along with !important selector', () => {
- let config = {
- prefix: 'tw-',
- content: [{ raw: html`
` }],
- safelist: [{ pattern: /^!tw-grid-cols-(4|5|6)$/ }],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .\!tw-grid-cols-4 {
- grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
- }
- .\!tw-grid-cols-5 {
- grid-template-columns: repeat(5, minmax(0, 1fr)) !important;
- }
- .\!tw-grid-cols-6 {
- grid-template-columns: repeat(6, minmax(0, 1fr)) !important;
- }
- .tw-uppercase {
- text-transform: uppercase;
- }
- `)
- })
- })
-
- it('should safelist pattern regex having !important selector with variants', () => {
- let config = {
- content: [{ raw: html`
` }],
- safelist: [
- {
- pattern: /^!bg-gray-(500|600|700|800)$/,
- variants: ['hover'],
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .\!bg-gray-500 {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(107 114 128 / var(--tw-bg-opacity)) !important;
- }
- .\!bg-gray-600 {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(75 85 99 / var(--tw-bg-opacity)) !important;
- }
- .\!bg-gray-700 {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(55 65 81 / var(--tw-bg-opacity)) !important;
- }
- .\!bg-gray-800 {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(31 41 55 / var(--tw-bg-opacity)) !important;
- }
- .uppercase {
- text-transform: uppercase;
- }
- .hover\:\!bg-gray-500:hover {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(107 114 128 / var(--tw-bg-opacity)) !important;
- }
- .hover\:\!bg-gray-600:hover {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(75 85 99 / var(--tw-bg-opacity)) !important;
- }
- .hover\:\!bg-gray-700:hover {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(55 65 81 / var(--tw-bg-opacity)) !important;
- }
- .hover\:\!bg-gray-800:hover {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(31 41 55 / var(--tw-bg-opacity)) !important;
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .\!bg-gray-500 {
- background-color: #6b7280 !important;
- }
- .\!bg-gray-600 {
- background-color: #4b5563 !important;
- }
- .\!bg-gray-700 {
- background-color: #374151 !important;
- }
- .\!bg-gray-800 {
- background-color: #1f2937 !important;
- }
- .uppercase {
- text-transform: uppercase;
- }
- .hover\:\!bg-gray-500:hover {
- background-color: #6b7280 !important;
- }
- .hover\:\!bg-gray-600:hover {
- background-color: #4b5563 !important;
- }
- .hover\:\!bg-gray-700:hover {
- background-color: #374151 !important;
- }
- .hover\:\!bg-gray-800:hover {
- background-color: #1f2937 !important;
- }
- `)
- })
- })
-
- it('should safelist multiple patterns with !important selector', () => {
- let config = {
- content: [{ raw: html`
` }],
- safelist: [
- {
- pattern: /^!text-gray-(700|800|900)$/,
- variants: ['hover'],
- },
- {
- pattern: /^!bg-gray-(200|300|400)$/,
- variants: ['hover'],
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .\!bg-gray-200 {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(229 231 235 / var(--tw-bg-opacity)) !important;
- }
- .\!bg-gray-300 {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(209 213 219 / var(--tw-bg-opacity)) !important;
- }
- .\!bg-gray-400 {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(156 163 175 / var(--tw-bg-opacity)) !important;
- }
- .uppercase {
- text-transform: uppercase;
- }
- .\!text-gray-700 {
- --tw-text-opacity: 1 !important;
- color: rgb(55 65 81 / var(--tw-text-opacity)) !important;
- }
- .\!text-gray-800 {
- --tw-text-opacity: 1 !important;
- color: rgb(31 41 55 / var(--tw-text-opacity)) !important;
- }
- .\!text-gray-900 {
- --tw-text-opacity: 1 !important;
- color: rgb(17 24 39 / var(--tw-text-opacity)) !important;
- }
- .hover\:\!bg-gray-200:hover {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(229 231 235 / var(--tw-bg-opacity)) !important;
- }
- .hover\:\!bg-gray-300:hover {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(209 213 219 / var(--tw-bg-opacity)) !important;
- }
- .hover\:\!bg-gray-400:hover {
- --tw-bg-opacity: 1 !important;
- background-color: rgb(156 163 175 / var(--tw-bg-opacity)) !important;
- }
- .hover\:\!text-gray-700:hover {
- --tw-text-opacity: 1 !important;
- color: rgb(55 65 81 / var(--tw-text-opacity)) !important;
- }
- .hover\:\!text-gray-800:hover {
- --tw-text-opacity: 1 !important;
- color: rgb(31 41 55 / var(--tw-text-opacity)) !important;
- }
- .hover\:\!text-gray-900:hover {
- --tw-text-opacity: 1 !important;
- color: rgb(17 24 39 / var(--tw-text-opacity)) !important;
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .\!bg-gray-200 {
- background-color: #e5e7eb !important;
- }
- .\!bg-gray-300 {
- background-color: #d1d5db !important;
- }
- .\!bg-gray-400 {
- background-color: #9ca3af !important;
- }
- .uppercase {
- text-transform: uppercase;
- }
- .\!text-gray-700 {
- color: #374151 !important;
- }
- .\!text-gray-800 {
- color: #1f2937 !important;
- }
- .\!text-gray-900 {
- color: #111827 !important;
- }
- .hover\:\!bg-gray-200:hover {
- background-color: #e5e7eb !important;
- }
- .hover\:\!bg-gray-300:hover {
- background-color: #d1d5db !important;
- }
- .hover\:\!bg-gray-400:hover {
- background-color: #9ca3af !important;
- }
- .hover\:\!text-gray-700:hover {
- color: #374151 !important;
- }
- .hover\:\!text-gray-800:hover {
- color: #1f2937 !important;
- }
- .hover\:\!text-gray-900:hover {
- color: #111827 !important;
- }
- `)
- })
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .uppercase {
+ text-transform: uppercase;
+ }
+ `)
+ })
+})
+
+it('should safelist strings', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ safelist: ['mt-[20px]', 'font-bold', 'text-gray-200', 'hover:underline'],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .mt-\[20px\] {
+ margin-top: 20px;
+ }
+ .font-bold {
+ font-weight: 700;
+ }
+ .uppercase {
+ text-transform: uppercase;
+ }
+ .text-gray-200 {
+ --tw-text-opacity: 1;
+ color: rgb(229 231 235 / var(--tw-text-opacity));
+ }
+ .hover\:underline:hover {
+ text-decoration-line: underline;
+ }
+ `)
+ })
+})
+
+it('should safelist based on a pattern regex', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ safelist: [
+ {
+ pattern: /^bg-(red)-(100|200)$/,
+ variants: ['hover'],
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-red-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 226 226 / var(--tw-bg-opacity));
+ }
+ .bg-red-200 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 202 202 / var(--tw-bg-opacity));
+ }
+ .uppercase {
+ text-transform: uppercase;
+ }
+ .hover\:bg-red-100:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 226 226 / var(--tw-bg-opacity));
+ }
+ .hover\:bg-red-200:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 202 202 / var(--tw-bg-opacity));
+ }
+ `)
+ })
+})
+
+it('should not generate duplicates', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ safelist: [
+ 'uppercase',
+ {
+ pattern: /^bg-(red)-(100|200)$/,
+ variants: ['hover'],
+ },
+ {
+ pattern: /^bg-(red)-(100|200)$/,
+ variants: ['hover'],
+ },
+ {
+ pattern: /^bg-(red)-(100|200)$/,
+ variants: ['hover'],
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-red-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 226 226 / var(--tw-bg-opacity));
+ }
+ .bg-red-200 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 202 202 / var(--tw-bg-opacity));
+ }
+ .uppercase {
+ text-transform: uppercase;
+ }
+ .hover\:bg-red-100:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 226 226 / var(--tw-bg-opacity));
+ }
+ .hover\:bg-red-200:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 202 202 / var(--tw-bg-opacity));
+ }
+ `)
+ })
+})
+
+it('should safelist when using a custom prefix', () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`
` }],
+ safelist: [
+ {
+ pattern: /^tw-bg-red-(100|200)$/g,
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .tw-bg-red-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 226 226 / var(--tw-bg-opacity));
+ }
+ .tw-bg-red-200 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 202 202 / var(--tw-bg-opacity));
+ }
+ .tw-uppercase {
+ text-transform: uppercase;
+ }
+ `)
+ })
+})
+
+it('should not safelist when an empty list is provided', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ safelist: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .uppercase {
+ text-transform: uppercase;
+ }
+ `)
+ })
+})
+
+it('should not safelist when an sparse/holey list is provided', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ safelist: [, , ,],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .uppercase {
+ text-transform: uppercase;
+ }
+ `)
+ })
+})
+
+it('should not safelist any invalid variants if provided', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ safelist: [
+ {
+ pattern: /^bg-(red)-(100|200)$/,
+ variants: ['foo', 'bar'],
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-red-100 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 226 226 / var(--tw-bg-opacity));
+ }
+ .bg-red-200 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(254 202 202 / var(--tw-bg-opacity));
+ }
+ .uppercase {
+ text-transform: uppercase;
+ }
+ `)
+ })
+})
+
+it('should safelist negatives based on a pattern regex', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ safelist: [
+ {
+ pattern: /^-top-1$/,
+ variants: ['hover'],
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .-top-1 {
+ top: -0.25rem;
+ }
+ .uppercase {
+ text-transform: uppercase;
+ }
+ .hover\:-top-1:hover {
+ top: -0.25rem;
+ }
+ `)
+ })
+})
+
+it('should safelist negatives based on a pattern regex', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ safelist: [
+ {
+ pattern: /^bg-red-(400|500)(\/(40|50))?$/,
+ variants: ['hover'],
+ },
+ {
+ pattern: /^(fill|ring|text)-red-200\/50$/,
+ variants: ['hover'],
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-red-400 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(248 113 113 / var(--tw-bg-opacity));
+ }
+ .bg-red-400\/40 {
+ background-color: #f8717166;
+ }
+ .bg-red-400\/50 {
+ background-color: #f8717180;
+ }
+ .bg-red-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(239 68 68 / var(--tw-bg-opacity));
+ }
+ .bg-red-500\/40 {
+ background-color: #ef444466;
+ }
+ .bg-red-500\/50 {
+ background-color: #ef444480;
+ }
+ .fill-red-200\/50 {
+ fill: #fecaca80;
+ }
+ .uppercase {
+ text-transform: uppercase;
+ }
+ .text-red-200\/50 {
+ color: #fecaca80;
+ }
+ .ring-red-200\/50 {
+ --tw-ring-color: #fecaca80;
+ }
+ .hover\:bg-red-400:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(248 113 113 / var(--tw-bg-opacity));
+ }
+ .hover\:bg-red-400\/40:hover {
+ background-color: #f8717166;
+ }
+ .hover\:bg-red-400\/50:hover {
+ background-color: #f8717180;
+ }
+ .hover\:bg-red-500:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(239 68 68 / var(--tw-bg-opacity));
+ }
+ .hover\:bg-red-500\/40:hover {
+ background-color: #ef444466;
+ }
+ .hover\:bg-red-500\/50:hover {
+ background-color: #ef444480;
+ }
+ .hover\:fill-red-200\/50:hover {
+ fill: #fecaca80;
+ }
+ .hover\:text-red-200\/50:hover {
+ color: #fecaca80;
+ }
+ .hover\:ring-red-200\/50:hover {
+ --tw-ring-color: #fecaca80;
+ }
+ `)
+ })
+})
+
+it('should safelist pattern regex with !important selector', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ safelist: [{ pattern: /^!grid-cols-(4|5|6)$/ }],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .\!grid-cols-4 {
+ grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
+ }
+ .\!grid-cols-5 {
+ grid-template-columns: repeat(5, minmax(0, 1fr)) !important;
+ }
+ .\!grid-cols-6 {
+ grid-template-columns: repeat(6, minmax(0, 1fr)) !important;
+ }
+ .uppercase {
+ text-transform: uppercase;
+ }
+ `)
+ })
+})
+
+it('should safelist pattern regex with custom prefix along with !important selector', () => {
+ let config = {
+ prefix: 'tw-',
+ content: [{ raw: html`
` }],
+ safelist: [{ pattern: /^!tw-grid-cols-(4|5|6)$/ }],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .\!tw-grid-cols-4 {
+ grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
+ }
+ .\!tw-grid-cols-5 {
+ grid-template-columns: repeat(5, minmax(0, 1fr)) !important;
+ }
+ .\!tw-grid-cols-6 {
+ grid-template-columns: repeat(6, minmax(0, 1fr)) !important;
+ }
+ .tw-uppercase {
+ text-transform: uppercase;
+ }
+ `)
+ })
+})
+
+it('should safelist pattern regex having !important selector with variants', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ safelist: [
+ {
+ pattern: /^!bg-gray-(500|600|700|800)$/,
+ variants: ['hover'],
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .\!bg-gray-500 {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(107 114 128 / var(--tw-bg-opacity)) !important;
+ }
+ .\!bg-gray-600 {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(75 85 99 / var(--tw-bg-opacity)) !important;
+ }
+ .\!bg-gray-700 {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(55 65 81 / var(--tw-bg-opacity)) !important;
+ }
+ .\!bg-gray-800 {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(31 41 55 / var(--tw-bg-opacity)) !important;
+ }
+ .uppercase {
+ text-transform: uppercase;
+ }
+ .hover\:\!bg-gray-500:hover {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(107 114 128 / var(--tw-bg-opacity)) !important;
+ }
+ .hover\:\!bg-gray-600:hover {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(75 85 99 / var(--tw-bg-opacity)) !important;
+ }
+ .hover\:\!bg-gray-700:hover {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(55 65 81 / var(--tw-bg-opacity)) !important;
+ }
+ .hover\:\!bg-gray-800:hover {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(31 41 55 / var(--tw-bg-opacity)) !important;
+ }
+ `)
+ })
+})
+
+it('should safelist multiple patterns with !important selector', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ safelist: [
+ {
+ pattern: /^!text-gray-(700|800|900)$/,
+ variants: ['hover'],
+ },
+ {
+ pattern: /^!bg-gray-(200|300|400)$/,
+ variants: ['hover'],
+ },
+ ],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .\!bg-gray-200 {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(229 231 235 / var(--tw-bg-opacity)) !important;
+ }
+ .\!bg-gray-300 {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(209 213 219 / var(--tw-bg-opacity)) !important;
+ }
+ .\!bg-gray-400 {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(156 163 175 / var(--tw-bg-opacity)) !important;
+ }
+ .uppercase {
+ text-transform: uppercase;
+ }
+ .\!text-gray-700 {
+ --tw-text-opacity: 1 !important;
+ color: rgb(55 65 81 / var(--tw-text-opacity)) !important;
+ }
+ .\!text-gray-800 {
+ --tw-text-opacity: 1 !important;
+ color: rgb(31 41 55 / var(--tw-text-opacity)) !important;
+ }
+ .\!text-gray-900 {
+ --tw-text-opacity: 1 !important;
+ color: rgb(17 24 39 / var(--tw-text-opacity)) !important;
+ }
+ .hover\:\!bg-gray-200:hover {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(229 231 235 / var(--tw-bg-opacity)) !important;
+ }
+ .hover\:\!bg-gray-300:hover {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(209 213 219 / var(--tw-bg-opacity)) !important;
+ }
+ .hover\:\!bg-gray-400:hover {
+ --tw-bg-opacity: 1 !important;
+ background-color: rgb(156 163 175 / var(--tw-bg-opacity)) !important;
+ }
+ .hover\:\!text-gray-700:hover {
+ --tw-text-opacity: 1 !important;
+ color: rgb(55 65 81 / var(--tw-text-opacity)) !important;
+ }
+ .hover\:\!text-gray-800:hover {
+ --tw-text-opacity: 1 !important;
+ color: rgb(31 41 55 / var(--tw-text-opacity)) !important;
+ }
+ .hover\:\!text-gray-900:hover {
+ --tw-text-opacity: 1 !important;
+ color: rgb(17 24 39 / var(--tw-text-opacity)) !important;
+ }
+ `)
})
})
diff --git a/tests/screenAtRule.test.js b/tests/screenAtRule.test.js
index 6352fcde0..af13b5ddf 100644
--- a/tests/screenAtRule.test.js
+++ b/tests/screenAtRule.test.js
@@ -1,60 +1,58 @@
import postcss from 'postcss'
import plugin from '../src/lib/substituteScreenAtRules'
import config from '../stubs/config.full.js'
-import { crosscheck, css } from './util/run'
+import { css } from './util/run'
function run(input, opts = config) {
return postcss([plugin({ tailwindConfig: opts })]).process(input, { from: undefined })
}
-crosscheck(() => {
- test('it can generate media queries from configured screen sizes', () => {
- let input = css`
- @screen sm {
+test('it can generate media queries from configured screen sizes', () => {
+ let input = css`
+ @screen sm {
+ .banana {
+ color: yellow;
+ }
+ }
+ @screen md {
+ .banana {
+ color: red;
+ }
+ }
+ @screen lg {
+ .banana {
+ color: green;
+ }
+ }
+ `
+
+ return run(input, {
+ theme: {
+ screens: {
+ sm: '500px',
+ md: '750px',
+ lg: '1000px',
+ },
+ },
+ separator: ':',
+ }).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 500px) {
.banana {
color: yellow;
}
}
- @screen md {
+ @media (min-width: 750px) {
.banana {
color: red;
}
}
- @screen lg {
+ @media (min-width: 1000px) {
.banana {
color: green;
}
}
- `
-
- return run(input, {
- theme: {
- screens: {
- sm: '500px',
- md: '750px',
- lg: '1000px',
- },
- },
- separator: ':',
- }).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 500px) {
- .banana {
- color: #ff0;
- }
- }
- @media (min-width: 750px) {
- .banana {
- color: red;
- }
- }
- @media (min-width: 1000px) {
- .banana {
- color: green;
- }
- }
- `)
- expect(result.warnings().length).toBe(0)
- })
+ `)
+ expect(result.warnings().length).toBe(0)
})
})
diff --git a/tests/shared-state.test.js b/tests/shared-state.test.js
index 4791d07dc..a3a9f2870 100644
--- a/tests/shared-state.test.js
+++ b/tests/shared-state.test.js
@@ -1,23 +1,20 @@
import { resolveDebug } from '../src/lib/sharedState'
-import { crosscheck } from './util/run'
-crosscheck(() => {
- it.each`
- value | expected
- ${'true'} | ${true}
- ${'1'} | ${true}
- ${'false'} | ${false}
- ${'0'} | ${false}
- ${'*'} | ${true}
- ${'tailwindcss'} | ${true}
- ${'tailwindcss:*'} | ${true}
- ${'other,tailwindcss'} | ${true}
- ${'other,tailwindcss:*'} | ${true}
- ${'other,-tailwindcss'} | ${false}
- ${'other,-tailwindcss:*'} | ${false}
- ${'-tailwindcss'} | ${false}
- ${'-tailwindcss:*'} | ${false}
- `('should resolve the debug ($value) flag correctly ($expected)', ({ value, expected }) => {
- expect(resolveDebug(value)).toBe(expected)
- })
+it.each`
+ value | expected
+ ${'true'} | ${true}
+ ${'1'} | ${true}
+ ${'false'} | ${false}
+ ${'0'} | ${false}
+ ${'*'} | ${true}
+ ${'tailwindcss'} | ${true}
+ ${'tailwindcss:*'} | ${true}
+ ${'other,tailwindcss'} | ${true}
+ ${'other,tailwindcss:*'} | ${true}
+ ${'other,-tailwindcss'} | ${false}
+ ${'other,-tailwindcss:*'} | ${false}
+ ${'-tailwindcss'} | ${false}
+ ${'-tailwindcss:*'} | ${false}
+`('should resolve the debug ($value) flag correctly ($expected)', ({ value, expected }) => {
+ expect(resolveDebug(value)).toBe(expected)
})
diff --git a/tests/source-maps.test.js b/tests/source-maps.test.js
index dc0092dbc..4cfcd62d8 100644
--- a/tests/source-maps.test.js
+++ b/tests/source-maps.test.js
@@ -1,532 +1,585 @@
import postcss from 'postcss'
import { parseSourceMaps } from './util/source-maps'
-import { crosscheck, runWithSourceMaps as run, html, css, map } from './util/run'
+import { runWithSourceMaps as run, html, css, map } from './util/run'
-crosscheck(({ stable, oxide }) => {
- oxide.test.todo('apply generates source maps')
- stable.test('apply generates source maps', async () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- .with-declaration {
- background-color: red;
- @apply h-4 w-4 bg-green-500;
- }
-
- .with-comment {
- /* sourcemap will work here too */
- @apply h-4 w-4 bg-red-500;
- }
-
- .just-apply {
- @apply h-4 w-4 bg-black;
- }
- `
-
- let result = await run(input, config)
- let { sources, annotations } = parseSourceMaps(result)
-
- // All CSS generated by Tailwind CSS should be annotated with source maps
- // And always be able to point to the original source file
- expect(sources).not.toContain('
')
- expect(sources.length).toBe(1)
-
- expect(annotations).toEqual([
- '2:6 -> 2:6',
- '3:8-29 -> 3:8-29',
- '4:8-35 -> 4:8-20',
- '4:8-35 -> 5:8-19',
- '4:8-35 -> 6:8-26',
- '4:8-35 -> 7:8-63',
- '5:6 -> 8:6',
- '7:6 -> 10:6',
- '8:8-41 -> 11:8-41',
- '9:8-33 -> 12:8-20',
- '9:8-33 -> 13:8-19',
- '9:8-33 -> 14:8-26',
- '9:8-33 -> 15:8-63',
- '10:6 -> 16:6',
- '13:8 -> 18:6',
- '13:8-31 -> 19:8-20',
- '13:8-31 -> 20:8-19',
- '13:8-31 -> 21:8-26',
- '13:8 -> 22:8',
- '13:31 -> 23:0',
- ])
- })
-
- oxide.test.todo('preflight + base have source maps')
- stable.test('preflight + base have source maps', async () => {
- let config = {
- content: [],
- }
-
- let input = css`
- @tailwind base;
- `
-
- let result = await run(input, config)
- let { sources, annotations } = parseSourceMaps(result)
-
- // All CSS generated by Tailwind CSS should be annotated with source maps
- // And always be able to point to the original source file
- expect(sources).not.toContain('')
- expect(sources.length).toBe(1)
-
- expect(annotations).toEqual([
- '2:6 -> 1:0',
- '2:20-6 -> 3:1-2',
- '2:20 -> 6:1',
- '2:6 -> 8:0',
- '2:6-20 -> 11:2-32',
- '2:6-20 -> 12:2-25',
- '2:6-20 -> 13:2-29',
- '2:6-20 -> 14:2-31',
- '2:20 -> 15:0',
- '2:6 -> 17:0',
- '2:6-20 -> 19:2-18',
- '2:20 -> 20:0',
- '2:6 -> 22:0',
- '2:20 -> 29:1',
- '2:6 -> 31:0',
- '2:6-20 -> 32:2-26',
- '2:6-20 -> 33:2-40',
- '2:6-20 -> 34:2-26',
- '2:6-20 -> 35:2-21',
- '2:6-20 -> 36:2-230',
- '2:6-20 -> 37:2-39',
- '2:6-20 -> 38:2-41',
- '2:20 -> 39:0',
- '2:6 -> 41:0',
- '2:20 -> 44:1',
- '2:6 -> 46:0',
- '2:6-20 -> 47:2-19',
- '2:6-20 -> 48:2-30',
- '2:20 -> 49:0',
- '2:6 -> 51:0',
- '2:20 -> 55:1',
- '2:6 -> 57:0',
- '2:6-20 -> 58:2-19',
- '2:6-20 -> 59:2-24',
- '2:6-20 -> 60:2-31',
- '2:20 -> 61:0',
- '2:6 -> 63:0',
- '2:20 -> 65:1',
- '2:6 -> 67:0',
- '2:6-20 -> 68:2-35',
- '2:20 -> 69:0',
- '2:6 -> 71:0',
- '2:20 -> 73:1',
- '2:6 -> 75:0',
- '2:6-20 -> 81:2-20',
- '2:6-20 -> 82:2-22',
- '2:20 -> 83:0',
- '2:6 -> 85:0',
- '2:20 -> 87:1',
- '2:6 -> 89:0',
- '2:6-20 -> 90:2-16',
- '2:6-20 -> 91:2-26',
- '2:20 -> 92:0',
- '2:6 -> 94:0',
- '2:20 -> 96:1',
- '2:6 -> 98:0',
- '2:6-20 -> 100:2-21',
- '2:20 -> 101:0',
- '2:6 -> 103:0',
- '2:20 -> 106:1',
- '2:6 -> 108:0',
- '2:6-20 -> 112:2-121',
- '2:6-20 -> 113:2-24',
- '2:20 -> 114:0',
- '2:6 -> 116:0',
- '2:20 -> 118:1',
- '2:6 -> 120:0',
- '2:6-20 -> 121:2-16',
- '2:20 -> 122:0',
- '2:6 -> 124:0',
- '2:20 -> 126:1',
- '2:6 -> 128:0',
- '2:6-20 -> 130:2-16',
- '2:6-20 -> 131:2-16',
- '2:6-20 -> 132:2-20',
- '2:6-20 -> 133:2-26',
- '2:20 -> 134:0',
- '2:6 -> 136:0',
- '2:6-20 -> 137:2-17',
- '2:20 -> 138:0',
- '2:6 -> 140:0',
- '2:6-20 -> 141:2-13',
- '2:20 -> 142:0',
- '2:6 -> 144:0',
- '2:20 -> 148:1',
- '2:6 -> 150:0',
- '2:6-20 -> 151:2-24',
- '2:6-20 -> 152:2-31',
- '2:6-20 -> 153:2-35',
- '2:20 -> 154:0',
- '2:6 -> 156:0',
- '2:20 -> 160:1',
- '2:6 -> 162:0',
- '2:6-20 -> 167:2-30',
- '2:6-20 -> 168:2-25',
- '2:6-20 -> 169:2-30',
- '2:6-20 -> 170:2-30',
- '2:6-20 -> 171:2-24',
- '2:6-20 -> 172:2-19',
- '2:6-20 -> 173:2-20',
- '2:20 -> 174:0',
- '2:6 -> 176:0',
- '2:20 -> 178:1',
- '2:6 -> 180:0',
- '2:6-20 -> 182:2-22',
- '2:20 -> 183:0',
- '2:6 -> 185:0',
- '2:20 -> 188:1',
- '2:6 -> 190:0',
- '2:6-20 -> 194:2-36',
- '2:6-20 -> 195:2-39',
- '2:6-20 -> 196:2-32',
- '2:20 -> 197:0',
- '2:6 -> 199:0',
- '2:20 -> 201:1',
- '2:6 -> 203:0',
- '2:6-20 -> 204:2-15',
- '2:20 -> 205:0',
- '2:6 -> 207:0',
- '2:20 -> 209:1',
- '2:6 -> 211:0',
- '2:6-20 -> 212:2-18',
- '2:20 -> 213:0',
- '2:6 -> 215:0',
- '2:20 -> 217:1',
- '2:6 -> 219:0',
- '2:6-20 -> 220:2-26',
- '2:20 -> 221:0',
- '2:6 -> 223:0',
- '2:20 -> 225:1',
- '2:6 -> 227:0',
- '2:6-20 -> 229:2-14',
- '2:20 -> 230:0',
- '2:6 -> 232:0',
- '2:20 -> 235:1',
- '2:6 -> 237:0',
- '2:6-20 -> 238:2-39',
- '2:6-20 -> 239:2-30',
- '2:20 -> 240:0',
- '2:6 -> 242:0',
- '2:20 -> 244:1',
- '2:6 -> 246:0',
- '2:6-20 -> 247:2-26',
- '2:20 -> 248:0',
- '2:6 -> 250:0',
- '2:20 -> 253:1',
- '2:6 -> 255:0',
- '2:6-20 -> 256:2-36',
- '2:6-20 -> 257:2-23',
- '2:20 -> 258:0',
- '2:6 -> 260:0',
- '2:20 -> 262:1',
- '2:6 -> 264:0',
- '2:6-20 -> 265:2-20',
- '2:20 -> 266:0',
- '2:6 -> 268:0',
- '2:20 -> 270:1',
- '2:6 -> 272:0',
- '2:6-20 -> 285:2-11',
- '2:20 -> 286:0',
- '2:6 -> 288:0',
- '2:6-20 -> 289:2-11',
- '2:6-20 -> 290:2-12',
- '2:20 -> 291:0',
- '2:6 -> 293:0',
- '2:6-20 -> 294:2-12',
- '2:20 -> 295:0',
- '2:6 -> 297:0',
- '2:6-20 -> 300:2-18',
- '2:6-20 -> 301:2-11',
- '2:6-20 -> 302:2-12',
- '2:20 -> 303:0',
- '2:6 -> 305:0',
- '2:20 -> 307:1',
- '2:6 -> 309:0',
- '2:6-20 -> 310:2-18',
- '2:20 -> 311:0',
- '2:6 -> 313:0',
- '2:20 -> 316:1',
- '2:6 -> 318:0',
- '2:6-20 -> 320:2-20',
- '2:6-20 -> 321:2-24',
- '2:20 -> 322:0',
- '2:6 -> 324:0',
- '2:20 -> 326:1',
- '2:6 -> 328:0',
- '2:6-20 -> 330:2-17',
- '2:20 -> 331:0',
- '2:6 -> 333:0',
- '2:20 -> 335:1',
- '2:6 -> 336:0',
- '2:6-20 -> 337:2-17',
- '2:20 -> 338:0',
- '2:6 -> 340:0',
- '2:20 -> 344:1',
- '2:6 -> 346:0',
- '2:6-20 -> 354:2-24',
- '2:6-20 -> 355:2-32',
- '2:20 -> 356:0',
- '2:6 -> 358:0',
- '2:20 -> 360:1',
- '2:6 -> 362:0',
- '2:6-20 -> 364:2-17',
- '2:6-20 -> 365:2-14',
- '2:20 -> 366:0',
- '2:6-20 -> 368:0-72',
- '2:6 -> 369:0',
- '2:6-20 -> 370:2-15',
- '2:20 -> 371:0',
- '2:6 -> 373:0',
- '2:6-20 -> 374:2-26',
- '2:6-20 -> 375:2-26',
- '2:6-20 -> 376:2-21',
- '2:6-20 -> 377:2-21',
- '2:6-20 -> 378:2-16',
- '2:6-20 -> 379:2-16',
- '2:6-20 -> 380:2-16',
- '2:6-20 -> 381:2-17',
- '2:6-20 -> 382:2-17',
- '2:6-20 -> 383:2-15',
- '2:6-20 -> 384:2-15',
- '2:6-20 -> 385:2-20',
- '2:6-20 -> 386:2-40',
- '2:6-20 -> 387:2-32',
- '2:6-20 -> 388:2-31',
- '2:6-20 -> 389:2-30',
- '2:6-20 -> 390:2-17',
- '2:6-20 -> 391:2-22',
- '2:6-20 -> 392:2-24',
- '2:6-20 -> 393:2-25',
- '2:6-20 -> 394:2-26',
- '2:6-20 -> 395:2-20',
- '2:6-20 -> 396:2-29',
- '2:6-20 -> 397:2-30',
- '2:6-20 -> 398:2-40',
- '2:6-20 -> 399:2-36',
- '2:6-20 -> 400:2-29',
- '2:6-20 -> 401:2-24',
- '2:6-20 -> 402:2-32',
- '2:6-20 -> 403:2-14',
- '2:6-20 -> 404:2-20',
- '2:6-20 -> 405:2-18',
- '2:6-20 -> 406:2-19',
- '2:6-20 -> 407:2-20',
- '2:6-20 -> 408:2-16',
- '2:6-20 -> 409:2-18',
- '2:6-20 -> 410:2-15',
- '2:6-20 -> 411:2-21',
- '2:6-20 -> 412:2-23',
- '2:6-20 -> 413:2-29',
- '2:6-20 -> 414:2-27',
- '2:6-20 -> 415:2-28',
- '2:6-20 -> 416:2-29',
- '2:6-20 -> 417:2-25',
- '2:6-20 -> 418:2-26',
- '2:6-20 -> 419:2-27',
- '2:6 -> 420:2',
- '2:20 -> 421:0',
- '2:6 -> 423:0',
- '2:6-20 -> 424:2-26',
- '2:6-20 -> 425:2-26',
- '2:6-20 -> 426:2-21',
- '2:6-20 -> 427:2-21',
- '2:6-20 -> 428:2-16',
- '2:6-20 -> 429:2-16',
- '2:6-20 -> 430:2-16',
- '2:6-20 -> 431:2-17',
- '2:6-20 -> 432:2-17',
- '2:6-20 -> 433:2-15',
- '2:6-20 -> 434:2-15',
- '2:6-20 -> 435:2-20',
- '2:6-20 -> 436:2-40',
- '2:6-20 -> 437:2-32',
- '2:6-20 -> 438:2-31',
- '2:6-20 -> 439:2-30',
- '2:6-20 -> 440:2-17',
- '2:6-20 -> 441:2-22',
- '2:6-20 -> 442:2-24',
- '2:6-20 -> 443:2-25',
- '2:6-20 -> 444:2-26',
- '2:6-20 -> 445:2-20',
- '2:6-20 -> 446:2-29',
- '2:6-20 -> 447:2-30',
- '2:6-20 -> 448:2-40',
- '2:6-20 -> 449:2-36',
- '2:6-20 -> 450:2-29',
- '2:6-20 -> 451:2-24',
- '2:6-20 -> 452:2-32',
- '2:6-20 -> 453:2-14',
- '2:6-20 -> 454:2-20',
- '2:6-20 -> 455:2-18',
- '2:6-20 -> 456:2-19',
- '2:6-20 -> 457:2-20',
- '2:6-20 -> 458:2-16',
- '2:6-20 -> 459:2-18',
- '2:6-20 -> 460:2-15',
- '2:6-20 -> 461:2-21',
- '2:6-20 -> 462:2-23',
- '2:6-20 -> 463:2-29',
- '2:6-20 -> 464:2-27',
- '2:6-20 -> 465:2-28',
- '2:6-20 -> 466:2-29',
- '2:6-20 -> 467:2-25',
- '2:6-20 -> 468:2-26',
- '2:6-20 -> 469:2-27',
- '2:6 -> 470:2',
- '2:20 -> 471:0',
- ])
- })
-
- oxide.test.todo('utilities have source maps')
- stable.test('utilities have source maps', async () => {
- let config = {
- content: [{ raw: `text-red-500` }],
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
- let { sources, annotations } = parseSourceMaps(result)
-
- // All CSS generated by Tailwind CSS should be annotated with source maps
- // And always be able to point to the original source file
- expect(sources).not.toContain('')
- expect(sources.length).toBe(1)
-
- expect(annotations).toStrictEqual([
- '2:6 -> 1:0',
- '2:6-25 -> 2:4-24',
- '2:6 -> 3:4',
- '2:25 -> 4:0',
- ])
- })
-
- oxide.test.todo('components have source maps')
- stable.test('components have source maps', async () => {
- let config = {
- content: [{ raw: `container` }],
- }
-
- let input = css`
- @tailwind components;
- `
-
- let result = await run(input, config)
- let { sources, annotations } = parseSourceMaps(result)
-
- // All CSS generated by Tailwind CSS should be annotated with source maps
- // And always be able to point to the original source file
- expect(sources).not.toContain('')
- expect(sources.length).toBe(1)
-
- expect(annotations).toEqual([
- '2:6 -> 1:0',
- '2:6 -> 2:4',
- '2:26 -> 3:0',
- '2:6 -> 4:0',
- '2:6 -> 5:4',
- '2:6 -> 6:8',
- '2:26 -> 7:4',
- '2:26 -> 8:0',
- '2:6 -> 9:0',
- '2:6 -> 10:4',
- '2:6 -> 11:8',
- '2:26 -> 12:4',
- '2:26 -> 13:0',
- '2:6 -> 14:0',
- '2:6 -> 15:4',
- '2:6 -> 16:8',
- '2:26 -> 17:4',
- '2:26 -> 18:0',
- '2:6 -> 19:0',
- '2:6 -> 20:4',
- '2:6 -> 21:8',
- '2:26 -> 22:4',
- '2:26 -> 23:0',
- '2:6 -> 24:0',
- '2:6 -> 25:4',
- '2:6 -> 26:8',
- '2:26 -> 27:4',
- '2:26 -> 28:0',
- ])
- })
-
- oxide.test.todo('source maps for layer rules are not rewritten to point to @tailwind directives')
- stable.test(
- 'source maps for layer rules are not rewritten to point to @tailwind directives',
- async () => {
- let config = {
- content: [{ raw: `font-normal foo hover:foo` }],
- }
-
- let utilitiesFile = postcss.parse(
- css`
- @tailwind utilities;
+test('apply generates source maps', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
`,
- { from: 'components.css', map: { prev: map } }
- )
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
- let mainCssFile = postcss.parse(
- css`
- @layer utilities {
- .foo {
- background-color: red;
- }
- }
- `,
- { from: 'input.css', map: { prev: map } }
- )
-
- // Just pretend that there's an @import in `mainCssFile` that imports the nodes from `utilitiesFile`
- let input = postcss.root({
- nodes: [...utilitiesFile.nodes, ...mainCssFile.nodes],
- source: mainCssFile.source,
- })
-
- let result = await run(input, config)
-
- let { sources, annotations } = parseSourceMaps(result)
-
- // All CSS generated by Tailwind CSS should be annotated with source maps
- // And always be able to point to the original source file
- expect(sources).not.toContain('')
-
- // And we should see that the source map for the layer rule is not rewritten
- // to point to the @tailwind directive but instead points to the original
- expect(sources.length).toBe(2)
- expect(sources).toEqual(['components.css', 'input.css'])
-
- expect(annotations).toEqual([
- '2:10 -> 1:0',
- '2:10 -> 2:14',
- '2:29 -> 3:0',
- '3:12 -> 4:12',
- '4:14-35 -> 5:14-35',
- '5:12 -> 6:12',
- '3:12 -> 7:12',
- '4:14-35 -> 8:14-35',
- '5:12 -> 9:12',
- ])
+ let input = css`
+ .with-declaration {
+ background-color: red;
+ @apply h-4 w-4 bg-green-500;
}
- )
+
+ .with-comment {
+ /* sourcemap will work here too */
+ @apply h-4 w-4 bg-red-500;
+ }
+
+ .just-apply {
+ @apply h-4 w-4 bg-black;
+ }
+ `
+
+ let result = await run(input, config)
+ let { sources, annotations } = parseSourceMaps(result)
+
+ // All CSS generated by Tailwind CSS should be annotated with source maps
+ // And always be able to point to the original source file
+ expect(sources).not.toContain('')
+ expect(sources.length).toBe(2)
+
+ expect(annotations).toMatchInlineSnapshot(`
+ [
+ "2:4 -> 1:0",
+ "2:2-20 -> 2:2-20",
+ "3:2-23 -> 3:2-23",
+ "4:2-57 -> 4:2-57",
+ "5:2-13 -> 5:2-13",
+ "6:2-14 -> 6:2-14",
+ "7:0 -> 7:0",
+ "7:4 -> 9:0",
+ "10:2-20 -> 10:2-20",
+ "11:2-57 -> 11:2-57",
+ "12:2-13 -> 12:2-13",
+ "13:2-14 -> 13:2-14",
+ "14:0 -> 14:0",
+ "13:6 -> 16:0",
+ "17:2-20 -> 17:2-20",
+ "18:2-53 -> 18:2-53",
+ "19:2-13 -> 19:2-13",
+ "20:2-14 -> 20:2-14",
+ "21:0 -> 21:0",
+ ]
+ `)
+})
+
+test('preflight + base have source maps', async () => {
+ let config = {
+ content: [],
+ }
+
+ let input = css`
+ @tailwind base;
+ `
+
+ let result = await run(input, config)
+ let { sources, annotations } = parseSourceMaps(result)
+
+ // All CSS generated by Tailwind CSS should be annotated with source maps
+ // And always be able to point to the original source file
+ expect(sources).not.toContain('')
+ expect(sources.length).toBe(2)
+
+ expect(annotations).toMatchInlineSnapshot(`
+ [
+ "2:4 -> 1:0",
+ "2:2-24 -> 2:2-24",
+ "3:2-25 -> 3:2-25",
+ "4:0 -> 4:0",
+ "2:4 -> 6:0",
+ "7:2-18 -> 7:2-18",
+ "8:0 -> 8:0",
+ "2:4 -> 10:0",
+ "11:2-32 -> 11:2-32",
+ "12:2-13 -> 12:2-13",
+ "13:2-31 -> 13:2-31",
+ "14:2-33 -> 14:2-33",
+ "15:2-208 -> 15:2-208",
+ "16:2-18 -> 16:2-18",
+ "17:0 -> 17:0",
+ "2:4 -> 19:0",
+ "20:2-22 -> 20:2-22",
+ "21:2-11 -> 21:2-11",
+ "22:0 -> 22:0",
+ "2:4 -> 24:0",
+ "25:2-16 -> 25:2-16",
+ "26:2-23 -> 26:2-23",
+ "27:2-11 -> 27:2-11",
+ "28:0 -> 28:0",
+ "2:4 -> 30:0",
+ "31:2-43 -> 31:2-43",
+ "32:2-35 -> 32:2-35",
+ "33:0 -> 33:0",
+ "2:4 -> 35:0",
+ "36:2-20 -> 36:2-20",
+ "37:2-22 -> 37:2-22",
+ "38:0 -> 38:0",
+ "2:4 -> 40:0",
+ "41:2-16 -> 41:2-16",
+ "42:2-34 -> 42:2-34",
+ "43:2-26 -> 43:2-26",
+ "44:0 -> 44:0",
+ "2:4 -> 46:0",
+ "47:2-21 -> 47:2-21",
+ "48:0 -> 48:0",
+ "2:4 -> 50:0",
+ "51:2-109 -> 51:2-109",
+ "52:2-16 -> 52:2-16",
+ "53:0 -> 53:0",
+ "2:4 -> 55:0",
+ "56:2-16 -> 56:2-16",
+ "57:0 -> 57:0",
+ "2:4 -> 59:0",
+ "60:2-26 -> 60:2-26",
+ "61:2-16 -> 61:2-16",
+ "62:2-16 -> 62:2-16",
+ "63:2-20 -> 63:2-20",
+ "64:0 -> 64:0",
+ "2:4 -> 66:0",
+ "67:2-16 -> 67:2-16",
+ "68:0 -> 68:0",
+ "2:4 -> 70:0",
+ "71:2-12 -> 71:2-12",
+ "72:0 -> 72:0",
+ "2:4 -> 74:0",
+ "75:2-16 -> 75:2-16",
+ "76:2-23 -> 76:2-23",
+ "77:2-27 -> 77:2-27",
+ "78:0 -> 78:0",
+ "2:4 -> 80:0",
+ "81:2-32 -> 81:2-32",
+ "82:2-34 -> 82:2-34",
+ "83:2-22 -> 83:2-22",
+ "84:2-17 -> 84:2-17",
+ "85:2-22 -> 85:2-22",
+ "86:2-22 -> 86:2-22",
+ "87:2-16 -> 87:2-16",
+ "88:2-11 -> 88:2-11",
+ "89:2-12 -> 89:2-12",
+ "90:0 -> 90:0",
+ "2:4 -> 92:0",
+ "93:2-22 -> 93:2-22",
+ "94:0 -> 94:0",
+ "2:4 -> 96:0",
+ "97:2-28 -> 97:2-28",
+ "98:2-25 -> 98:2-25",
+ "99:2-24 -> 99:2-24",
+ "100:0 -> 100:0",
+ "2:4 -> 102:0",
+ "103:2-15 -> 103:2-15",
+ "104:0 -> 104:0",
+ "2:4 -> 106:0",
+ "107:2-18 -> 107:2-18",
+ "108:0 -> 108:0",
+ "2:4 -> 110:0",
+ "111:2-26 -> 111:2-26",
+ "112:0 -> 112:0",
+ "2:4 -> 114:0",
+ "115:2-14 -> 115:2-14",
+ "116:0 -> 116:0",
+ "2:4 -> 118:0",
+ "119:2-31 -> 119:2-31",
+ "120:2-22 -> 120:2-22",
+ "121:0 -> 121:0",
+ "2:4 -> 123:0",
+ "124:2-26 -> 124:2-26",
+ "125:0 -> 125:0",
+ "2:4 -> 127:0",
+ "128:2-28 -> 128:2-28",
+ "129:2-15 -> 129:2-15",
+ "130:0 -> 130:0",
+ "2:4 -> 132:0",
+ "133:2-20 -> 133:2-20",
+ "134:0 -> 134:0",
+ "2:4 -> 136:0",
+ "137:2-11 -> 137:2-11",
+ "138:0 -> 138:0",
+ "2:4 -> 140:0",
+ "141:2-11 -> 141:2-11",
+ "142:2-12 -> 142:2-12",
+ "143:0 -> 143:0",
+ "2:4 -> 145:0",
+ "146:2-12 -> 146:2-12",
+ "147:0 -> 147:0",
+ "2:4 -> 149:0",
+ "150:2-11 -> 150:2-11",
+ "151:2-12 -> 151:2-12",
+ "152:2-18 -> 152:2-18",
+ "153:0 -> 153:0",
+ "2:4 -> 155:0",
+ "156:2-12 -> 156:2-12",
+ "157:0 -> 157:0",
+ "2:4 -> 159:0",
+ "160:2-18 -> 160:2-18",
+ "161:0 -> 161:0",
+ "2:4 -> 163:0",
+ "164:2-12 -> 164:2-12",
+ "165:2-16 -> 165:2-16",
+ "166:0 -> 166:0",
+ "2:4 -> 168:0",
+ "169:2-17 -> 169:2-17",
+ "170:0 -> 170:0",
+ "2:4 -> 172:0",
+ "173:2-17 -> 173:2-17",
+ "174:0 -> 174:0",
+ "2:4 -> 176:0",
+ "177:2-24 -> 177:2-24",
+ "178:2-16 -> 178:2-16",
+ "179:0 -> 179:0",
+ "2:4 -> 181:0",
+ "182:2-17 -> 182:2-17",
+ "183:2-14 -> 183:2-14",
+ "184:0 -> 184:0",
+ "2:4 -> 186:0",
+ "187:2-15 -> 187:2-15",
+ "188:0 -> 188:0",
+ "2:4 -> 190:0",
+ "191:2-26 -> 191:2-26",
+ "192:2-26 -> 192:2-26",
+ "193:2-21 -> 193:2-21",
+ "194:2-21 -> 194:2-21",
+ "195:2-16 -> 195:2-16",
+ "196:2-16 -> 196:2-16",
+ "197:2-16 -> 197:2-16",
+ "198:2-17 -> 198:2-17",
+ "199:2-17 -> 199:2-17",
+ "200:2-14 -> 200:2-14",
+ "201:2-14 -> 201:2-14",
+ "202:2-19 -> 202:2-19",
+ "203:2-40 -> 203:2-40",
+ "204:2-31 -> 204:2-31",
+ "205:2-30 -> 205:2-30",
+ "206:2-29 -> 206:2-29",
+ "207:2-16 -> 207:2-16",
+ "208:2-21 -> 208:2-21",
+ "209:2-23 -> 209:2-23",
+ "210:2-24 -> 210:2-24",
+ "211:2-25 -> 211:2-25",
+ "212:2-19 -> 212:2-19",
+ "213:2-29 -> 213:2-29",
+ "214:2-30 -> 214:2-30",
+ "215:2-28 -> 215:2-28",
+ "216:2-36 -> 216:2-36",
+ "217:2-29 -> 217:2-29",
+ "218:2-24 -> 218:2-24",
+ "219:2-32 -> 219:2-32",
+ "220:2-13 -> 220:2-13",
+ "221:2-19 -> 221:2-19",
+ "222:2-17 -> 222:2-17",
+ "223:2-18 -> 223:2-18",
+ "224:2-19 -> 224:2-19",
+ "225:2-15 -> 225:2-15",
+ "226:2-17 -> 226:2-17",
+ "227:2-14 -> 227:2-14",
+ "228:2-20 -> 228:2-20",
+ "229:2-22 -> 229:2-22",
+ "230:2-28 -> 230:2-28",
+ "231:2-26 -> 231:2-26",
+ "232:2-27 -> 232:2-27",
+ "233:2-28 -> 233:2-28",
+ "234:2-24 -> 234:2-24",
+ "235:2-25 -> 235:2-25",
+ "236:2-26 -> 236:2-26",
+ "237:2-23 -> 237:2-23",
+ "238:0 -> 238:0",
+ "2:4 -> 240:0",
+ "241:2-26 -> 241:2-26",
+ "242:2-26 -> 242:2-26",
+ "243:2-21 -> 243:2-21",
+ "244:2-21 -> 244:2-21",
+ "245:2-16 -> 245:2-16",
+ "246:2-16 -> 246:2-16",
+ "247:2-16 -> 247:2-16",
+ "248:2-17 -> 248:2-17",
+ "249:2-17 -> 249:2-17",
+ "250:2-14 -> 250:2-14",
+ "251:2-14 -> 251:2-14",
+ "252:2-19 -> 252:2-19",
+ "253:2-40 -> 253:2-40",
+ "254:2-31 -> 254:2-31",
+ "255:2-30 -> 255:2-30",
+ "256:2-29 -> 256:2-29",
+ "257:2-16 -> 257:2-16",
+ "258:2-21 -> 258:2-21",
+ "259:2-23 -> 259:2-23",
+ "260:2-24 -> 260:2-24",
+ "261:2-25 -> 261:2-25",
+ "262:2-19 -> 262:2-19",
+ "263:2-29 -> 263:2-29",
+ "264:2-30 -> 264:2-30",
+ "265:2-28 -> 265:2-28",
+ "266:2-36 -> 266:2-36",
+ "267:2-29 -> 267:2-29",
+ "268:2-24 -> 268:2-24",
+ "269:2-32 -> 269:2-32",
+ "270:2-13 -> 270:2-13",
+ "271:2-19 -> 271:2-19",
+ "272:2-17 -> 272:2-17",
+ "273:2-18 -> 273:2-18",
+ "274:2-19 -> 274:2-19",
+ "275:2-15 -> 275:2-15",
+ "276:2-17 -> 276:2-17",
+ "277:2-14 -> 277:2-14",
+ "278:2-20 -> 278:2-20",
+ "279:2-22 -> 279:2-22",
+ "280:2-28 -> 280:2-28",
+ "281:2-26 -> 281:2-26",
+ "282:2-27 -> 282:2-27",
+ "283:2-28 -> 283:2-28",
+ "284:2-24 -> 284:2-24",
+ "285:2-25 -> 285:2-25",
+ "286:2-26 -> 286:2-26",
+ "287:2-23 -> 287:2-23",
+ "288:0 -> 288:0",
+ "2:4 -> 290:0",
+ "291:2-26 -> 291:2-26",
+ "292:2-26 -> 292:2-26",
+ "293:2-21 -> 293:2-21",
+ "294:2-21 -> 294:2-21",
+ "295:2-16 -> 295:2-16",
+ "296:2-16 -> 296:2-16",
+ "297:2-16 -> 297:2-16",
+ "298:2-17 -> 298:2-17",
+ "299:2-17 -> 299:2-17",
+ "300:2-14 -> 300:2-14",
+ "301:2-14 -> 301:2-14",
+ "302:2-19 -> 302:2-19",
+ "303:2-40 -> 303:2-40",
+ "304:2-31 -> 304:2-31",
+ "305:2-30 -> 305:2-30",
+ "306:2-29 -> 306:2-29",
+ "307:2-16 -> 307:2-16",
+ "308:2-21 -> 308:2-21",
+ "309:2-23 -> 309:2-23",
+ "310:2-24 -> 310:2-24",
+ "311:2-25 -> 311:2-25",
+ "312:2-19 -> 312:2-19",
+ "313:2-29 -> 313:2-29",
+ "314:2-30 -> 314:2-30",
+ "315:2-28 -> 315:2-28",
+ "316:2-36 -> 316:2-36",
+ "317:2-29 -> 317:2-29",
+ "318:2-24 -> 318:2-24",
+ "319:2-32 -> 319:2-32",
+ "320:2-13 -> 320:2-13",
+ "321:2-19 -> 321:2-19",
+ "322:2-17 -> 322:2-17",
+ "323:2-18 -> 323:2-18",
+ "324:2-19 -> 324:2-19",
+ "325:2-15 -> 325:2-15",
+ "326:2-17 -> 326:2-17",
+ "327:2-14 -> 327:2-14",
+ "328:2-20 -> 328:2-20",
+ "329:2-22 -> 329:2-22",
+ "330:2-28 -> 330:2-28",
+ "331:2-26 -> 331:2-26",
+ "332:2-27 -> 332:2-27",
+ "333:2-28 -> 333:2-28",
+ "334:2-24 -> 334:2-24",
+ "335:2-25 -> 335:2-25",
+ "336:2-26 -> 336:2-26",
+ "337:2-23 -> 337:2-23",
+ "338:0 -> 338:0",
+ ]
+ `)
+})
+
+test('utilities have source maps', async () => {
+ let config = {
+ content: [{ raw: `text-red-500` }],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+ let { sources, annotations } = parseSourceMaps(result)
+
+ // All CSS generated by Tailwind CSS should be annotated with source maps
+ // And always be able to point to the original source file
+ expect(sources).not.toContain('')
+ expect(sources.length).toBe(2)
+
+ expect(annotations).toMatchInlineSnapshot(`
+ [
+ "2:4 -> 1:0",
+ "2:2-22 -> 2:2-22",
+ "3:2-48 -> 3:2-48",
+ "4:0 -> 4:0",
+ ]
+ `)
+})
+
+test('components have source maps', async () => {
+ let config = {
+ content: [{ raw: `container` }],
+ }
+
+ let input = css`
+ @tailwind components;
+ `
+
+ let result = await run(input, config)
+ let { sources, annotations } = parseSourceMaps(result)
+
+ // All CSS generated by Tailwind CSS should be annotated with source maps
+ // And always be able to point to the original source file
+ expect(sources).not.toContain('')
+ expect(sources.length).toBe(2)
+
+ expect(annotations).toMatchInlineSnapshot(`
+ [
+ "2:4 -> 1:0",
+ "2:2-13 -> 2:2-13",
+ "3:0 -> 3:0",
+ "2:4 -> 5:0",
+ "2:4 -> 6:2",
+ "7:4-20 -> 7:4-20",
+ "8:2 -> 8:2",
+ "9:0 -> 9:0",
+ "2:4 -> 11:0",
+ "2:4 -> 12:2",
+ "13:4-20 -> 13:4-20",
+ "14:2 -> 14:2",
+ "15:0 -> 15:0",
+ "2:4 -> 17:0",
+ "2:4 -> 18:2",
+ "19:4-21 -> 19:4-21",
+ "20:2 -> 20:2",
+ "21:0 -> 21:0",
+ "2:4 -> 23:0",
+ "2:4 -> 24:2",
+ "25:4-21 -> 25:4-21",
+ "26:2 -> 26:2",
+ "27:0 -> 27:0",
+ "2:4 -> 29:0",
+ "2:4 -> 30:2",
+ "31:4-21 -> 31:4-21",
+ "32:2 -> 32:2",
+ "33:0 -> 33:0",
+ ]
+ `)
+})
+
+test('source maps for layer rules are not rewritten to point to @tailwind directives', async () => {
+ let config = {
+ content: [{ raw: `font-normal foo hover:foo` }],
+ }
+
+ let utilitiesFile = postcss.parse(
+ css`
+ @tailwind utilities;
+ `,
+ { from: 'components.css', map: { prev: map } }
+ )
+
+ let mainCssFile = postcss.parse(
+ css`
+ @layer utilities {
+ .foo {
+ background-color: red;
+ }
+ }
+ `,
+ { from: 'input.css', map: { prev: map } }
+ )
+
+ // Just pretend that there's an @import in `mainCssFile` that imports the nodes from `utilitiesFile`
+ let input = postcss.root({
+ nodes: [...utilitiesFile.nodes, ...mainCssFile.nodes],
+ source: mainCssFile.source,
+ })
+
+ let result = await run(input, config)
+
+ let { sources, annotations } = parseSourceMaps(result)
+
+ // All CSS generated by Tailwind CSS should be annotated with source maps
+ // And always be able to point to the original source file
+ expect(sources).not.toContain('')
+
+ // And we should see that the source map for the layer rule is not rewritten
+ // to point to the @tailwind directive but instead points to the original
+ expect(sources.length).toBe(3)
+ expect(sources).toMatchInlineSnapshot(`
+ [
+ "components.css",
+ "source-maps.test.js%3Ftest=c291cmNlIG1hcHMgZm9yIGxheWVyIHJ1bGVzIGFyZSBub3QgcmV3cml0dGVuIHRvIHBvaW50IHRvIEB0YWlsd2luZCBkaXJlY3RpdmVz",
+ "input.css",
+ ]
+ `)
+
+ expect(annotations).toMatchInlineSnapshot(`
+ [
+ "2:6 -> 1:0",
+ "2:2-18 -> 2:2-18",
+ "3:0 -> 3:0",
+ "3:8 -> 5:0",
+ "6:2-23 -> 6:2-23",
+ "7:0 -> 7:0",
+ ]
+ `)
+})
+
+test('it handles `map: true` correctly', async () => {
+ let config = {
+ content: [{ raw: `text-red-500` }],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config, {
+ map: true,
+ })
+ let { sources, annotations } = parseSourceMaps(result)
+
+ // All CSS generated by Tailwind CSS should be annotated with source maps
+ // And always be able to point to the original source file
+ expect(sources).not.toContain('')
+ expect(sources.length).toBe(2)
+
+ expect(annotations).toMatchInlineSnapshot(`
+ [
+ "2:4 -> 1:0",
+ "2:2-22 -> 2:2-22",
+ "3:2-48 -> 3:2-48",
+ "4:0 -> 4:0",
+ ]
+ `)
+})
+
+test('it handles `map: { inline: true }` correctly', async () => {
+ let config = {
+ content: [{ raw: `text-red-500` }],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config, {
+ map: {
+ inline: true,
+ },
+ })
+ let { sources, annotations } = parseSourceMaps(result)
+
+ // All CSS generated by Tailwind CSS should be annotated with source maps
+ // And always be able to point to the original source file
+ expect(sources).not.toContain('')
+ expect(sources.length).toBe(2)
+
+ expect(annotations).toMatchInlineSnapshot(`
+ [
+ "2:4 -> 1:0",
+ "2:2-22 -> 2:2-22",
+ "3:2-48 -> 3:2-48",
+ "4:0 -> 4:0",
+ ]
+ `)
})
diff --git a/tests/space-utilities.test.js b/tests/space-utilities.test.js
new file mode 100644
index 000000000..194c63ce2
--- /dev/null
+++ b/tests/space-utilities.test.js
@@ -0,0 +1,43 @@
+import { run, html, css, defaults } from './util/run'
+
+test('space-x uses non-logical properties', () => {
+ let config = {
+ future: {
+ logicalSiblingUtilities: false,
+ },
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ return run('@tailwind base; @tailwind utilities;', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults}
+ .space-x-4 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-x-reverse: 0;
+ margin-right: calc(1rem * var(--tw-space-x-reverse));
+ margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
+ }
+ `)
+ })
+})
+
+test('space-x uses logical properties', () => {
+ let config = {
+ future: {
+ logicalSiblingUtilities: true,
+ },
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ return run('@tailwind base; @tailwind utilities;', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ ${defaults}
+ .space-x-4 > :not([hidden]) ~ :not([hidden]) {
+ --tw-space-x-reverse: 0;
+ margin-inline-start: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
+ margin-inline-end: calc(1rem * var(--tw-space-x-reverse));
+ }
+ `)
+ })
+})
diff --git a/tests/syntax-lit-html.test.js b/tests/syntax-lit-html.test.js
index 952a9b969..a3bdfebda 100644
--- a/tests/syntax-lit-html.test.js
+++ b/tests/syntax-lit-html.test.js
@@ -1,72 +1,45 @@
-import { crosscheck, run, css } from './util/run'
+import { run, css } from './util/run'
-crosscheck(({ stable, oxide }) => {
- test('it detects classes in lit-html templates', () => {
- let config = {
- content: [
- {
- raw: `html\`\${data.title} \`;`,
- },
- ],
- corePlugins: { preflight: false },
- theme: {},
- plugins: [],
- }
+test('it detects classes in lit-html templates', () => {
+ let config = {
+ content: [
+ {
+ raw: `html\`\${data.title} \`;`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {},
+ plugins: [],
+ }
- return run('@tailwind utilities', config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .rounded {
- border-radius: 0.25rem;
- }
- .bg-blue-400 {
- --tw-bg-opacity: 1;
- background-color: rgb(96 165 250 / var(--tw-bg-opacity));
- }
- .px-4 {
- padding-left: 1rem;
- padding-right: 1rem;
- }
- .py-2 {
- padding-top: 0.5rem;
- padding-bottom: 0.5rem;
- }
- .font-bold {
- font-weight: 700;
- }
- .text-white {
- --tw-text-opacity: 1;
- color: rgb(255 255 255 / var(--tw-text-opacity));
- }
- .hover\:bg-blue-600:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(37 99 235 / var(--tw-bg-opacity));
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .rounded {
- border-radius: 0.25rem;
- }
- .bg-blue-400 {
- background-color: #60a5fa;
- }
- .px-4 {
- padding-left: 1rem;
- padding-right: 1rem;
- }
- .py-2 {
- padding-top: 0.5rem;
- padding-bottom: 0.5rem;
- }
- .font-bold {
- font-weight: 700;
- }
- .text-white {
- color: #fff;
- }
- .hover\:bg-blue-600:hover {
- background-color: #2563eb;
- }
- `)
- })
+ return run('@tailwind utilities', config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .rounded {
+ border-radius: 0.25rem;
+ }
+ .bg-blue-400 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(96 165 250 / var(--tw-bg-opacity));
+ }
+ .px-4 {
+ padding-left: 1rem;
+ padding-right: 1rem;
+ }
+ .py-2 {
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+ }
+ .font-bold {
+ font-weight: 700;
+ }
+ .text-white {
+ --tw-text-opacity: 1;
+ color: rgb(255 255 255 / var(--tw-text-opacity));
+ }
+ .hover\:bg-blue-600:hover {
+ --tw-bg-opacity: 1;
+ background-color: rgb(37 99 235 / var(--tw-bg-opacity));
+ }
+ `)
})
})
diff --git a/tests/syntax-svelte.test.js b/tests/syntax-svelte.test.js
index be2c33e8b..b6df41f13 100644
--- a/tests/syntax-svelte.test.js
+++ b/tests/syntax-svelte.test.js
@@ -1,43 +1,40 @@
import path from 'path'
-import { crosscheck, run, css } from './util/run'
+import { run, css } from './util/run'
-crosscheck(({ stable, oxide }) => {
- oxide.test.todo('it detects svelte based on the file extension')
- stable.test('it detects svelte based on the file extension', () => {
- let config = {
- content: [path.resolve(__dirname, './syntax-svelte.test.svelte')],
- corePlugins: { preflight: false },
- theme: {},
- plugins: [],
- }
+test('it detects svelte based on the file extension', () => {
+ let config = {
+ content: [path.resolve(__dirname, './syntax-svelte.test.svelte')],
+ corePlugins: { preflight: false },
+ theme: {},
+ plugins: [],
+ }
- let input = css`
- @tailwind components;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind components;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchCss(css`
- .bg-red-500 {
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-red-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(239 68 68 / var(--tw-bg-opacity));
+ }
+ @media (min-width: 1024px) {
+ .lg\:hover\:bg-blue-500:hover {
--tw-bg-opacity: 1;
- background-color: rgb(239 68 68 / var(--tw-bg-opacity));
+ background-color: rgb(59 130 246 / var(--tw-bg-opacity));
}
- @media (min-width: 1024px) {
- .lg\:hover\:bg-blue-500:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(59 130 246 / var(--tw-bg-opacity));
- }
- }
- `)
- })
+ }
+ `)
})
+})
- oxide.test.todo('using raw with svelte extension')
- stable.test('using raw with svelte extension', () => {
- let config = {
- content: [
- {
- raw: `
+test('using raw with svelte extension', () => {
+ let config = {
+ content: [
+ {
+ raw: `
@@ -50,32 +47,31 @@ crosscheck(({ stable, oxide }) => {
Click me
`,
- extension: 'svelte',
- },
- ],
- corePlugins: { preflight: false },
- theme: {},
- plugins: [],
- }
+ extension: 'svelte',
+ },
+ ],
+ corePlugins: { preflight: false },
+ theme: {},
+ plugins: [],
+ }
- let input = css`
- @tailwind components;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind components;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- expect(result.css).toMatchCss(css`
- .bg-red-500 {
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .bg-red-500 {
+ --tw-bg-opacity: 1;
+ background-color: rgb(239 68 68 / var(--tw-bg-opacity));
+ }
+ @media (min-width: 1024px) {
+ .lg\:hover\:bg-blue-500:hover {
--tw-bg-opacity: 1;
- background-color: rgb(239 68 68 / var(--tw-bg-opacity));
+ background-color: rgb(59 130 246 / var(--tw-bg-opacity));
}
- @media (min-width: 1024px) {
- .lg\:hover\:bg-blue-500:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(59 130 246 / var(--tw-bg-opacity));
- }
- }
- `)
- })
+ }
+ `)
})
})
diff --git a/tests/tailwind-screens.test.js b/tests/tailwind-screens.test.js
index f968fd0e3..009c02bc3 100644
--- a/tests/tailwind-screens.test.js
+++ b/tests/tailwind-screens.test.js
@@ -1,65 +1,63 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(() => {
- test('class variants are inserted at `@tailwind variants`', async () => {
- let config = {
- content: [{ raw: html`
` }],
+test('class variants are inserted at `@tailwind variants`', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ @tailwind variants;
+ .foo {
+ color: black;
}
+ `
- let input = css`
- @tailwind utilities;
- @tailwind variants;
- .foo {
- color: black;
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold,
+ .hover\:font-bold:hover {
+ font-weight: 700;
}
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .font-bold,
- .hover\:font-bold:hover {
+ @media (min-width: 768px) {
+ .md\:font-bold {
font-weight: 700;
}
- @media (min-width: 768px) {
- .md\:font-bold {
- font-weight: 700;
- }
- }
- .foo {
- color: #000;
- }
- `)
- })
- })
-
- test('`@tailwind screens` works as an alias for `@tailwind variants`', async () => {
- let config = {
- content: [{ raw: html`
` }],
- }
-
- let input = css`
- @tailwind utilities;
- @tailwind screens;
- .foo {
- color: black;
}
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .font-bold,
- .hover\:font-bold:hover {
- font-weight: 700;
- }
- @media (min-width: 768px) {
- .md\:font-bold {
- font-weight: 700;
- }
- }
- .foo {
- color: #000;
- }
- `)
- })
+ .foo {
+ color: #000;
+ }
+ `)
+ })
+})
+
+test('`@tailwind screens` works as an alias for `@tailwind variants`', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ @tailwind screens;
+ .foo {
+ color: black;
+ }
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .font-bold,
+ .hover\:font-bold:hover {
+ font-weight: 700;
+ }
+ @media (min-width: 768px) {
+ .md\:font-bold {
+ font-weight: 700;
+ }
+ }
+ .foo {
+ color: #000;
+ }
+ `)
})
})
diff --git a/tests/to-path.test.js b/tests/to-path.test.js
index 1e6021ba9..8737194ed 100644
--- a/tests/to-path.test.js
+++ b/tests/to-path.test.js
@@ -1,21 +1,18 @@
import { toPath } from '../src/util/toPath'
-import { crosscheck } from './util/run'
-crosscheck(() => {
- it('should keep an array as an array', () => {
- let input = ['a', 'b', '0', 'c']
+it('should keep an array as an array', () => {
+ let input = ['a', 'b', '0', 'c']
- expect(toPath(input)).toBe(input)
- })
-
- it.each`
- input | output
- ${'a.b.c'} | ${['a', 'b', 'c']}
- ${'a[0].b.c'} | ${['a', '0', 'b', 'c']}
- ${'.a'} | ${['a']}
- ${'[].a'} | ${['a']}
- ${'a[1.5][b][c]'} | ${['a', '1.5', 'b', 'c']}
- `('should convert "$input" to "$output"', ({ input, output }) => {
- expect(toPath(input)).toEqual(output)
- })
+ expect(toPath(input)).toBe(input)
+})
+
+it.each`
+ input | output
+ ${'a.b.c'} | ${['a', 'b', 'c']}
+ ${'a[0].b.c'} | ${['a', '0', 'b', 'c']}
+ ${'.a'} | ${['a']}
+ ${'[].a'} | ${['a']}
+ ${'a[1.5][b][c]'} | ${['a', '1.5', 'b', 'c']}
+`('should convert "$input" to "$output"', ({ input, output }) => {
+ expect(toPath(input)).toEqual(output)
})
diff --git a/tests/util/apply-important-selector.test.js b/tests/util/apply-important-selector.test.js
index 0ec22792f..0622d802e 100644
--- a/tests/util/apply-important-selector.test.js
+++ b/tests/util/apply-important-selector.test.js
@@ -1,24 +1,25 @@
-import { crosscheck } from '../util/run'
import { applyImportantSelector } from '../../src/util/applyImportantSelector'
-crosscheck(() => {
- it.each`
- before | after
- ${'.foo'} | ${'#app :is(.foo)'}
- ${'.foo .bar'} | ${'#app :is(.foo .bar)'}
- ${'.foo:hover'} | ${'#app :is(.foo:hover)'}
- ${'.foo .bar:hover'} | ${'#app :is(.foo .bar:hover)'}
- ${'.foo::before'} | ${'#app :is(.foo)::before'}
- ${'.foo::before'} | ${'#app :is(.foo)::before'}
- ${'.foo::file-selector-button'} | ${'#app :is(.foo)::file-selector-button'}
- ${'.foo::-webkit-progress-bar'} | ${'#app :is(.foo)::-webkit-progress-bar'}
- ${'.foo:hover::before'} | ${'#app :is(.foo:hover)::before'}
- ${':is(.dark :is([dir="rtl"] .foo::before))'} | ${'#app :is(.dark :is([dir="rtl"] .foo))::before'}
- ${':is(.dark .foo) .bar'} | ${'#app :is(:is(.dark .foo) .bar)'}
- ${':is(.foo) :is(.bar)'} | ${'#app :is(:is(.foo) :is(.bar))'}
- ${':is(.foo)::before'} | ${'#app :is(.foo)::before'}
- ${'.foo:before'} | ${'#app :is(.foo):before'}
- `('should generate "$after" from "$before"', ({ before, after }) => {
- expect(applyImportantSelector(before, '#app')).toEqual(after)
- })
+it.each`
+ before | after
+ ${'.foo'} | ${'#app :is(.foo)'}
+ ${'.foo .bar'} | ${'#app :is(.foo .bar)'}
+ ${'.foo:hover'} | ${'#app :is(.foo:hover)'}
+ ${'.foo .bar:hover'} | ${'#app :is(.foo .bar:hover)'}
+ ${'.foo::before'} | ${'#app :is(.foo)::before'}
+ ${'.foo::before'} | ${'#app :is(.foo)::before'}
+ ${'.foo::file-selector-button'} | ${'#app :is(.foo)::file-selector-button'}
+ ${'.foo::-webkit-progress-bar'} | ${'#app :is(.foo)::-webkit-progress-bar'}
+ ${'.foo:hover::before'} | ${'#app :is(.foo:hover)::before'}
+ ${':is(.dark :is([dir="rtl"] .foo::before))'} | ${'#app :is(.dark :is([dir="rtl"] .foo))::before'}
+ ${':is(.dark .foo) .bar'} | ${'#app :is(:is(.dark .foo) .bar)'}
+ ${':is(.foo) :is(.bar)'} | ${'#app :is(:is(.foo) :is(.bar))'}
+ ${':is(.foo)::before'} | ${'#app :is(.foo)::before'}
+ ${'.foo:before'} | ${'#app :is(.foo):before'}
+ ${'.foo::some-uknown-pseudo'} | ${'#app :is(.foo)::some-uknown-pseudo'}
+ ${'.foo::some-uknown-pseudo:hover'} | ${'#app :is(.foo)::some-uknown-pseudo:hover'}
+ ${'.foo:focus::some-uknown-pseudo:hover'} | ${'#app :is(.foo:focus)::some-uknown-pseudo:hover'}
+ ${'.foo:hover::some-uknown-pseudo:focus'} | ${'#app :is(.foo:hover)::some-uknown-pseudo:focus'}
+`('should generate "$after" from "$before"', ({ before, after }) => {
+ expect(applyImportantSelector(before, '#app')).toEqual(after)
})
diff --git a/tests/util/crosscheck.test.js b/tests/util/crosscheck.test.js
deleted file mode 100644
index 032473386..000000000
--- a/tests/util/crosscheck.test.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import { crosscheck } from './run'
-
-crosscheck(({ stable, oxide, engine }) => {
- stable.test('should run on stable', () => {
- expect(engine.stable).toBe(true)
- expect(engine.oxide).toBe(false)
- })
- oxide.test('should run on oxide', () => {
- expect(engine.stable).toBe(false)
- expect(engine.oxide).toBe(true)
- })
- test('should run on both', () => {
- oxide.expect(engine.oxide).toBe(true)
- oxide.expect(engine.stable).toBe(false)
- stable.expect(engine.oxide).toBe(false)
- stable.expect(engine.stable).toBe(true)
- })
-})
diff --git a/tests/util/defaults.js b/tests/util/defaults.js
index bea97d338..77749244c 100644
--- a/tests/util/defaults.js
+++ b/tests/util/defaults.js
@@ -9,7 +9,106 @@ export function defaults({ defaultRingColor = `#3b82f680` } = {}) {
return css`
*,
:before,
- :after,
+ :after {
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --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-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-gradient-from-position: ;
+ --tw-gradient-via-position: ;
+ --tw-gradient-to-position: ;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: ${defaultRingColor};
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+ }
+
+ ::-webkit-backdrop {
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --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-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-gradient-from-position: ;
+ --tw-gradient-via-position: ;
+ --tw-gradient-to-position: ;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: ${defaultRingColor};
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+ }
+
::backdrop {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
diff --git a/tests/util/run.js b/tests/util/run.js
index 118edcb75..12594ec18 100644
--- a/tests/util/run.js
+++ b/tests/util/run.js
@@ -1,7 +1,5 @@
-import path from 'path'
import postcss from 'postcss'
import tailwind from '../../src'
-import { env } from '../../src/lib/sharedState'
export * from './strings'
export * from './defaults'
@@ -14,81 +12,28 @@ export let map = JSON.stringify({
mappings: '',
})
-globalThis.__OXIDE__ = env.ENGINE === 'oxide'
-
export function run(input, config, plugin = tailwind) {
- let { currentTestName } = expect.getState()
+ let { currentTestName, testPath } = expect.getState()
+ let path = `${testPath}?test=${Buffer.from(currentTestName).toString('base64')}`
return postcss(plugin(config)).process(input, {
- from: `${path.resolve(__filename)}?test=${currentTestName}`,
+ from: path,
+ to: path,
})
}
-export function runWithSourceMaps(input, config, plugin = tailwind) {
- let { currentTestName } = expect.getState()
+export function runWithSourceMaps(
+ input,
+ config,
+ options = { map: { prev: map } },
+ plugin = tailwind
+) {
+ let { currentTestName, testPath } = expect.getState()
+ let path = `${testPath}?test=${Buffer.from(currentTestName).toString('base64')}`
return postcss(plugin(config)).process(input, {
- from: `${path.resolve(__filename)}?test=${currentTestName}`,
- map: {
- prev: map,
- },
- })
-}
-
-let nullTest = Object.assign(function () {}, {
- skip: () => {},
- only: () => {},
- each: () => () => {},
- todo: () => {},
-})
-let nullProxy = new Proxy(
- {
- test: nullTest,
- it: nullTest,
- xit: nullTest.skip,
- fit: nullTest.only,
- xdescribe: nullTest.skip,
- fdescribe: nullTest.only,
- },
- {
- get(target, prop, _receiver) {
- if (prop in target) {
- return target[prop]
- }
- return Object.assign(() => {
- return nullProxy
- }, nullProxy)
- },
- }
-)
-
-/**
- * @typedef {object} CrossCheck
- * @property {typeof import('@jest/globals')} oxide
- * @property {typeof import('@jest/globals')} stable
- * @property {object} engine
- * @property {boolean} engine.oxide
- * @property {boolean} engine.stable
- */
-
-/**
- * @param {(data: CrossCheck) => void} fn
- */
-export function crosscheck(fn) {
- let engines =
- env.ENGINE === 'oxide' ? [{ engine: 'Stable' }, { engine: 'Oxide' }] : [{ engine: 'Stable' }]
-
- describe.each(engines)('$engine', ({ engine }) => {
- let engines = {
- oxide: engine === 'Oxide' ? globalThis : nullProxy,
- stable: engine === 'Stable' ? globalThis : nullProxy,
- engine: { oxide: engine === 'Oxide', stable: engine === 'Stable' },
- }
-
- beforeEach(() => {
- globalThis.__OXIDE__ = engines.engine.oxide
- })
-
- fn(engines)
+ from: path,
+ to: path,
+ ...options,
})
}
diff --git a/tests/util/source-maps.js b/tests/util/source-maps.js
index 4ed263126..fe460d844 100644
--- a/tests/util/source-maps.js
+++ b/tests/util/source-maps.js
@@ -6,7 +6,16 @@ import { SourceMapConsumer } from 'source-map-js'
* @param {import('postcss').Result} result
*/
export function parseSourceMaps(result) {
- let map = result.map.toJSON()
+ let map = result.map
+ ? result.map.toJSON()
+ : (() => {
+ let css = result.toString()
+ let sourceMappingURL = css.match(/\/\*# sourceMappingURL=(.*) \*\//)?.[1]
+ let raw = sourceMappingURL
+ ? Buffer.from(sourceMappingURL.split(',')[1], 'base64').toString()
+ : null
+ return JSON.parse(raw ?? '{}')
+ })()
return {
sources: map.sources,
@@ -15,7 +24,7 @@ export function parseSourceMaps(result) {
}
/**
- * An string annotation that represents a source map
+ * A string annotation that represents a source map
*
* It's not meant to be exhaustive just enough to
* verify that the source map is working and that
diff --git a/tests/variants.oxide.test.css b/tests/variants.oxide.test.css
deleted file mode 100644
index 5e06f3942..000000000
--- a/tests/variants.oxide.test.css
+++ /dev/null
@@ -1,412 +0,0 @@
-*,
-:before,
-:after,
-::backdrop {
- --tw-border-spacing-x: 0;
- --tw-border-spacing-y: 0;
- --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-pan-x: ;
- --tw-pan-y: ;
- --tw-pinch-zoom: ;
- --tw-scroll-snap-strictness: proximity;
- --tw-gradient-from-position: ;
- --tw-gradient-via-position: ;
- --tw-gradient-to-position: ;
- --tw-ordinal: ;
- --tw-slashed-zero: ;
- --tw-numeric-figure: ;
- --tw-numeric-spacing: ;
- --tw-numeric-fraction: ;
- --tw-ring-inset: ;
- --tw-ring-offset-width: 0px;
- --tw-ring-offset-color: #fff;
- --tw-ring-color: #3b82f680;
- --tw-ring-offset-shadow: 0 0 #0000;
- --tw-ring-shadow: 0 0 #0000;
- --tw-shadow: 0 0 #0000;
- --tw-shadow-colored: 0 0 #0000;
- --tw-blur: ;
- --tw-brightness: ;
- --tw-contrast: ;
- --tw-grayscale: ;
- --tw-hue-rotate: ;
- --tw-invert: ;
- --tw-saturate: ;
- --tw-sepia: ;
- --tw-drop-shadow: ;
- --tw-backdrop-blur: ;
- --tw-backdrop-brightness: ;
- --tw-backdrop-contrast: ;
- --tw-backdrop-grayscale: ;
- --tw-backdrop-hue-rotate: ;
- --tw-backdrop-invert: ;
- --tw-backdrop-opacity: ;
- --tw-backdrop-saturate: ;
- --tw-backdrop-sepia: ;
-}
-.first-letter\:text-2xl:first-letter {
- font-size: 1.5rem;
- line-height: 2rem;
-}
-.first-letter\:text-red-500:first-letter {
- color: #ef4444;
-}
-.first-line\:bg-yellow-300:first-line {
- background-color: #fde047;
-}
-.first-line\:underline:first-line {
- text-decoration-line: underline;
-}
-.marker\:text-lg ::marker {
- font-size: 1.125rem;
- line-height: 1.75rem;
-}
-.marker\:text-red-500 ::marker {
- color: #ef4444;
-}
-.marker\:text-lg::marker {
- font-size: 1.125rem;
- line-height: 1.75rem;
-}
-.marker\:text-red-500::marker {
- color: #ef4444;
-}
-.selection\:bg-blue-500 ::selection {
- background-color: #3b82f6;
-}
-.selection\:text-white ::selection {
- color: #fff;
-}
-.selection\:bg-blue-500::selection {
- background-color: #3b82f6;
-}
-.selection\:text-white::selection {
- color: #fff;
-}
-.file\:bg-blue-500::file-selector-button {
- background-color: #3b82f6;
-}
-.file\:text-white::file-selector-button {
- color: #fff;
-}
-.placeholder\:font-bold::placeholder {
- font-weight: 700;
-}
-.placeholder\:text-red-500::placeholder {
- color: #ef4444;
-}
-.backdrop\:shadow-md::backdrop {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
-}
-.before\:block:before {
- content: var(--tw-content);
- display: block;
-}
-.before\:bg-red-500:before {
- content: var(--tw-content);
- background-color: #ef4444;
-}
-.after\:flex:after {
- content: var(--tw-content);
- display: flex;
-}
-.after\:uppercase:after {
- content: var(--tw-content);
- text-transform: uppercase;
-}
-.first\:shadow-md:first-child,
-.last\:shadow-md:last-child,
-.only\:shadow-md:only-child,
-.odd\:shadow-md:nth-child(2n + 1),
-.even\:shadow-md:nth-child(2n),
-.first-of-type\:shadow-md:first-of-type,
-.last-of-type\:shadow-md:last-of-type,
-.only-of-type\:shadow-md:only-of-type,
-.visited\:shadow-md:visited,
-.target\:shadow-md:target {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
-}
-.open\:bg-red-200[open] {
- background-color: #fecaca;
-}
-.default\:shadow-md:default,
-.checked\:shadow-md:checked,
-.indeterminate\:shadow-md:indeterminate,
-.placeholder-shown\:shadow-md:placeholder-shown,
-.autofill\:shadow-md:autofill,
-.optional\:shadow-md:optional,
-.required\:shadow-md:required,
-.valid\:shadow-md:valid,
-.invalid\:shadow-md:invalid,
-.in-range\:shadow-md:in-range,
-.out-of-range\:shadow-md:out-of-range,
-.read-only\:shadow-md:read-only,
-.empty\:shadow-md:empty,
-.focus-within\:shadow-md:focus-within {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
-}
-@keyframes spin {
- to {
- transform: rotate(360deg);
- }
-}
-.hover\:animate-spin:hover {
- animation: 1s linear infinite spin;
-}
-.hover\:shadow-md:hover {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
-}
-.file\:hover\:bg-blue-600:hover::file-selector-button {
- background-color: #2563eb;
-}
-.open\:hover\:bg-red-200:hover[open] {
- background-color: #fecaca;
-}
-.focus\:shadow-md:focus,
-.focus\:hover\:shadow-md:hover:focus,
-.focus-visible\:shadow-md:focus-visible,
-.active\:shadow-md:active,
-.enabled\:shadow-md:enabled,
-.disabled\:shadow-md:disabled,
-.group:first-child .group-first\:shadow-md,
-.group:last-child .group-last\:shadow-md,
-.group:only-child .group-only\:shadow-md,
-.group:nth-child(2n + 1) .group-odd\:shadow-md,
-.group:nth-child(2n) .group-even\:shadow-md,
-.group:first-of-type .group-first-of-type\:shadow-md,
-.group:last-of-type .group-last-of-type\:shadow-md,
-.group:only-of-type .group-only-of-type\:shadow-md,
-.group:visited .group-visited\:shadow-md,
-.group:target .group-target\:shadow-md {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
-}
-.group[open] .group-open\:bg-red-200 {
- background-color: #fecaca;
-}
-.group:default .group-default\:shadow-md,
-.group:checked .group-checked\:shadow-md,
-.group:indeterminate .group-indeterminate\:shadow-md,
-.group:placeholder-shown .group-placeholder-shown\:shadow-md,
-.group:autofill .group-autofill\:shadow-md,
-.group:optional .group-optional\:shadow-md,
-.group:required .group-required\:shadow-md,
-.group:valid .group-valid\:shadow-md,
-.group:invalid .group-invalid\:shadow-md,
-.group:in-range .group-in-range\:shadow-md,
-.group:out-of-range .group-out-of-range\:shadow-md,
-.group:read-only .group-read-only\:shadow-md,
-.group:empty .group-empty\:shadow-md,
-.group:focus-within .group-focus-within\:shadow-md,
-.group:hover .group-hover\:shadow-md {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
-}
-.group[open]:hover .group-open\:group-hover\:space-x-2 > :not([hidden]) ~ :not([hidden]) {
- --tw-space-x-reverse: 0;
- margin-inline-start: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
- margin-inline-end: calc(0.5rem * var(--tw-space-x-reverse));
-}
-.group:focus .group-focus\:shadow-md {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
-}
-.group[open]:focus .group-open\:group-focus\:bg-red-200 {
- background-color: #fecaca;
-}
-.group:focus:hover .group-focus\:group-hover\:shadow-md,
-.group:focus-visible .group-focus-visible\:shadow-md,
-.group:active .group-active\:shadow-md,
-.group:enabled .group-enabled\:shadow-md,
-.group:disabled .group-disabled\:shadow-md,
-.group:disabled:focus:hover .group-disabled\:group-focus\:group-hover\:shadow-md,
-.group:disabled:focus:hover .group-disabled\:group-focus\:group-hover\:first\:shadow-md:first-child,
-.peer:first-child ~ .peer-first\:shadow-md,
-.peer:last-child ~ .peer-last\:shadow-md,
-.peer:only-child ~ .peer-only\:shadow-md,
-.peer:nth-child(2n + 1) ~ .peer-odd\:shadow-md,
-.peer:nth-child(2n) ~ .peer-even\:shadow-md,
-.peer:first-of-type ~ .peer-first-of-type\:shadow-md,
-.peer:last-of-type ~ .peer-last-of-type\:shadow-md,
-.peer:only-of-type ~ .peer-only-of-type\:shadow-md,
-.peer:visited ~ .peer-visited\:shadow-md,
-.peer:target ~ .peer-target\:shadow-md {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
-}
-.peer[open] ~ .peer-open\:bg-red-200 {
- background-color: #fecaca;
-}
-.peer:default ~ .peer-default\:shadow-md,
-.peer:checked ~ .peer-checked\:shadow-md,
-.peer:indeterminate ~ .peer-indeterminate\:shadow-md,
-.peer:placeholder-shown ~ .peer-placeholder-shown\:shadow-md,
-.peer:autofill ~ .peer-autofill\:shadow-md,
-.peer:optional ~ .peer-optional\:shadow-md,
-.peer:required ~ .peer-required\:shadow-md,
-.peer:valid ~ .peer-valid\:shadow-md,
-.peer:invalid ~ .peer-invalid\:shadow-md,
-.peer:in-range ~ .peer-in-range\:shadow-md,
-.peer:out-of-range ~ .peer-out-of-range\:shadow-md,
-.peer:read-only ~ .peer-read-only\:shadow-md,
-.peer:empty ~ .peer-empty\:shadow-md,
-.peer:focus-within ~ .peer-focus-within\:shadow-md,
-.peer:hover ~ .peer-hover\:shadow-md,
-.peer:focus ~ .peer-focus\:shadow-md,
-.peer:focus:hover ~ .peer-focus\:peer-hover\:shadow-md,
-.peer:focus-visible ~ .peer-focus-visible\:shadow-md,
-.peer:active ~ .peer-active\:shadow-md,
-.peer:enabled ~ .peer-enabled\:shadow-md,
-.peer:disabled ~ .peer-disabled\:shadow-md,
-.peer:disabled:focus:hover ~ .peer-disabled\:peer-focus\:peer-hover\:shadow-md,
-.peer:disabled:focus:hover ~ .peer-disabled\:peer-focus\:peer-hover\:first\:shadow-md:first-child,
-:is([dir='ltr'] .ltr\:shadow-md),
-:is([dir='rtl'] .rtl\:shadow-md) {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
-}
-@media (prefers-reduced-motion: no-preference) {
- .motion-safe\:shadow-md {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
- 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
-}
-@media (prefers-reduced-motion: reduce) {
- .motion-reduce\:shadow-md {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
- 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
-}
-@media (prefers-contrast: more) {
- .contrast-more\:bg-yellow-300 {
- background-color: #fde047;
- }
-}
-@media (prefers-contrast: less) {
- .contrast-less\:bg-yellow-300 {
- background-color: #fde047;
- }
-}
-:is(.dark .dark\:shadow-md),
-:is(.dark .group:disabled:focus:hover .dark\:group-disabled\:group-focus\:group-hover\:shadow-md),
-:is(.dark .peer:disabled:focus:hover ~ .dark\:peer-disabled\:peer-focus\:peer-hover\:shadow-md) {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
-}
-@media print {
- .print\:bg-yellow-300 {
- background-color: #fde047;
- }
-}
-@media (min-width: 640px) {
- .sm\:shadow-md,
- .sm\:active\:shadow-md:active {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
- 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
-}
-@media (min-width: 768px) {
- .md\:shadow-md,
- .group:focus .md\:group-focus\:shadow-md {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
- 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
-}
-@media (min-width: 1024px) {
- @keyframes spin {
- to {
- transform: rotate(360deg);
- }
- }
- .lg\:animate-spin {
- animation: 1s linear infinite spin;
- }
- .lg\:shadow-md,
- :is(.dark .lg\:dark\:shadow-md) {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
- 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
-}
-@media (min-width: 1280px) {
- .xl\:shadow-md,
- :is(.dark .xl\:dark\:disabled\:shadow-md:disabled) {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
- 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
-}
-@media (min-width: 1536px) {
- .\32 xl\:shadow-md {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
- 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
- @media (prefers-reduced-motion: no-preference) {
- :is(.dark .\32 xl\:dark\:motion-safe\:focus-within\:shadow-md:focus-within) {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
- 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
- }
- }
-}
-@media (orientation: portrait) {
- .portrait\:bg-yellow-300 {
- background-color: #fde047;
- }
-}
-@media (orientation: landscape) {
- .landscape\:bg-yellow-300 {
- background-color: #fde047;
- }
-}
diff --git a/tests/variants.test.css b/tests/variants.test.css
index 8a9d481ee..22fc0691d 100644
--- a/tests/variants.test.css
+++ b/tests/variants.test.css
@@ -1,6 +1,103 @@
*,
:before,
-:after,
+:after {
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --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-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-gradient-from-position: ;
+ --tw-gradient-via-position: ;
+ --tw-gradient-to-position: ;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: #3b82f680;
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+}
+::-webkit-backdrop {
+ --tw-border-spacing-x: 0;
+ --tw-border-spacing-y: 0;
+ --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-pan-x: ;
+ --tw-pan-y: ;
+ --tw-pinch-zoom: ;
+ --tw-scroll-snap-strictness: proximity;
+ --tw-gradient-from-position: ;
+ --tw-gradient-via-position: ;
+ --tw-gradient-to-position: ;
+ --tw-ordinal: ;
+ --tw-slashed-zero: ;
+ --tw-numeric-figure: ;
+ --tw-numeric-spacing: ;
+ --tw-numeric-fraction: ;
+ --tw-ring-inset: ;
+ --tw-ring-offset-width: 0px;
+ --tw-ring-offset-color: #fff;
+ --tw-ring-color: #3b82f680;
+ --tw-ring-offset-shadow: 0 0 #0000;
+ --tw-ring-shadow: 0 0 #0000;
+ --tw-shadow: 0 0 #0000;
+ --tw-shadow-colored: 0 0 #0000;
+ --tw-blur: ;
+ --tw-brightness: ;
+ --tw-contrast: ;
+ --tw-grayscale: ;
+ --tw-hue-rotate: ;
+ --tw-invert: ;
+ --tw-saturate: ;
+ --tw-sepia: ;
+ --tw-drop-shadow: ;
+ --tw-backdrop-blur: ;
+ --tw-backdrop-brightness: ;
+ --tw-backdrop-contrast: ;
+ --tw-backdrop-grayscale: ;
+ --tw-backdrop-hue-rotate: ;
+ --tw-backdrop-invert: ;
+ --tw-backdrop-opacity: ;
+ --tw-backdrop-saturate: ;
+ --tw-backdrop-sepia: ;
+}
::backdrop {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
@@ -50,384 +147,316 @@
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
}
-.first-letter\:text-2xl:first-letter {
- font-size: 1.5rem;
- line-height: 2rem;
+.first-letter\:flex:first-letter,
+.first-line\:flex:first-line {
+ display: flex;
}
-.first-letter\:text-red-500:first-letter {
- --tw-text-opacity: 1;
- color: rgb(239 68 68 / var(--tw-text-opacity));
+.marker\:flex ::marker {
+ display: flex;
}
-.first-line\:bg-yellow-300:first-line {
- --tw-bg-opacity: 1;
- background-color: rgb(253 224 71 / var(--tw-bg-opacity));
+.marker\:flex::marker {
+ display: flex;
}
-.first-line\:underline:first-line {
- text-decoration-line: underline;
+.selection\:flex ::selection {
+ display: flex;
}
-.marker\:text-lg ::marker {
- font-size: 1.125rem;
- line-height: 1.75rem;
+.selection\:flex::selection {
+ display: flex;
}
-.marker\:text-red-500 ::marker {
- color: #ef4444;
+.file\:flex::-webkit-file-upload-button {
+ display: flex;
}
-.marker\:text-lg::marker {
- font-size: 1.125rem;
- line-height: 1.75rem;
+.file\:flex::file-selector-button {
+ display: flex;
}
-.marker\:text-red-500::marker {
- color: #ef4444;
+.placeholder\:flex::placeholder {
+ display: flex;
}
-.selection\:bg-blue-500 ::selection {
- --tw-bg-opacity: 1;
- background-color: rgb(59 130 246 / var(--tw-bg-opacity));
+.backdrop\:flex::-webkit-backdrop {
+ display: flex;
}
-.selection\:text-white ::selection {
- --tw-text-opacity: 1;
- color: rgb(255 255 255 / var(--tw-text-opacity));
-}
-.selection\:bg-blue-500::selection {
- --tw-bg-opacity: 1;
- background-color: rgb(59 130 246 / var(--tw-bg-opacity));
-}
-.selection\:text-white::selection {
- --tw-text-opacity: 1;
- color: rgb(255 255 255 / var(--tw-text-opacity));
-}
-.file\:bg-blue-500::file-selector-button {
- --tw-bg-opacity: 1;
- background-color: rgb(59 130 246 / var(--tw-bg-opacity));
-}
-.file\:text-white::file-selector-button {
- --tw-text-opacity: 1;
- color: rgb(255 255 255 / var(--tw-text-opacity));
-}
-.placeholder\:font-bold::placeholder {
- font-weight: 700;
-}
-.placeholder\:text-red-500::placeholder {
- --tw-text-opacity: 1;
- color: rgb(239 68 68 / var(--tw-text-opacity));
-}
-.backdrop\:shadow-md::backdrop {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
-}
-.before\:block:before {
- content: var(--tw-content);
- display: block;
-}
-.before\:bg-red-500:before {
- content: var(--tw-content);
- --tw-bg-opacity: 1;
- background-color: rgb(239 68 68 / var(--tw-bg-opacity));
+.backdrop\:flex::backdrop {
+ display: flex;
}
+.before\:flex:before,
.after\:flex:after {
content: var(--tw-content);
display: flex;
}
-.after\:uppercase:after {
- content: var(--tw-content);
- text-transform: uppercase;
+.first\:flex:first-child,
+.last\:flex:last-child,
+.only\:flex:only-child,
+.odd\:flex:nth-child(odd),
+.even\:flex:nth-child(2n),
+.first-of-type\:flex:first-of-type,
+.last-of-type\:flex:last-of-type,
+.only-of-type\:flex:only-of-type,
+.visited\:flex:visited,
+.target\:flex:target,
+.open\:flex[open],
+.default\:flex:default,
+.checked\:flex:checked,
+.indeterminate\:flex:indeterminate,
+.placeholder-shown\:flex:placeholder-shown {
+ display: flex;
}
-.first\:shadow-md:first-child,
-.last\:shadow-md:last-child,
-.only\:shadow-md:only-child,
-.odd\:shadow-md:nth-child(2n + 1),
-.even\:shadow-md:nth-child(2n),
-.first-of-type\:shadow-md:first-of-type,
-.last-of-type\:shadow-md:last-of-type,
-.only-of-type\:shadow-md:only-of-type,
-.visited\:shadow-md:visited,
-.target\:shadow-md:target {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
+.autofill\:flex:-webkit-autofill {
+ display: flex;
}
-.open\:bg-red-200[open] {
- --tw-bg-opacity: 1;
- background-color: rgb(254 202 202 / var(--tw-bg-opacity));
+.autofill\:flex:autofill {
+ display: flex;
}
-.default\:shadow-md:default,
-.checked\:shadow-md:checked,
-.indeterminate\:shadow-md:indeterminate,
-.placeholder-shown\:shadow-md:placeholder-shown,
-.autofill\:shadow-md:autofill,
-.optional\:shadow-md:optional,
-.required\:shadow-md:required,
-.valid\:shadow-md:valid,
-.invalid\:shadow-md:invalid,
-.in-range\:shadow-md:in-range,
-.out-of-range\:shadow-md:out-of-range,
-.read-only\:shadow-md:read-only,
-.empty\:shadow-md:empty,
-.focus-within\:shadow-md:focus-within {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
+.optional\:flex:optional,
+.required\:flex:required,
+.valid\:flex:valid,
+.invalid\:flex:invalid,
+.in-range\:flex:in-range,
+.out-of-range\:flex:out-of-range,
+.read-only\:flex:read-only,
+.empty\:flex:empty,
+.focus-within\:flex:focus-within,
+.hover\:flex:hover {
+ display: flex;
}
-@keyframes spin {
- to {
- transform: rotate(360deg);
- }
+.file\:hover\:flex:hover::-webkit-file-upload-button {
+ display: flex;
}
-.hover\:animate-spin:hover {
- animation: 1s linear infinite spin;
+.file\:hover\:flex:hover::file-selector-button {
+ display: flex;
}
-.hover\:shadow-md:hover {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
+.open\:hover\:flex:hover[open],
+.focus\:flex:focus,
+.focus\:hover\:flex:hover:focus {
+ display: flex;
}
-.file\:hover\:bg-blue-600:hover::file-selector-button {
- --tw-bg-opacity: 1;
- background-color: rgb(37 99 235 / var(--tw-bg-opacity));
+.focus-visible\:flex:focus-visible {
+ display: flex;
}
-.open\:hover\:bg-red-200:hover[open] {
- --tw-bg-opacity: 1;
- background-color: rgb(254 202 202 / var(--tw-bg-opacity));
+.active\:flex:active,
+.enabled\:flex:enabled,
+.disabled\:flex:disabled,
+.group:first-child .group-first\:flex,
+.group:last-child .group-last\:flex,
+.group:only-child .group-only\:flex,
+.group:nth-child(odd) .group-odd\:flex,
+.group:nth-child(2n) .group-even\:flex,
+.group:first-of-type .group-first-of-type\:flex,
+.group:last-of-type .group-last-of-type\:flex,
+.group:only-of-type .group-only-of-type\:flex,
+.group:visited .group-visited\:flex,
+.group:target .group-target\:flex,
+.group[open] .group-open\:flex,
+.group:default .group-default\:flex,
+.group:checked .group-checked\:flex,
+.group:indeterminate .group-indeterminate\:flex,
+.group:placeholder-shown .group-placeholder-shown\:flex,
+.group:-webkit-autofill .group-autofill\:flex,
+.group:optional .group-optional\:flex,
+.group:required .group-required\:flex,
+.group:valid .group-valid\:flex,
+.group:invalid .group-invalid\:flex,
+.group:in-range .group-in-range\:flex,
+.group:out-of-range .group-out-of-range\:flex,
+.group:read-only .group-read-only\:flex,
+.group:empty .group-empty\:flex,
+.group:focus-within .group-focus-within\:flex,
+.group:hover .group-hover\:flex,
+.group[open]:hover .group-open\:group-hover\:flex,
+.group:focus .group-focus\:flex,
+.group[open]:focus .group-open\:group-focus\:flex,
+.group:focus:hover .group-focus\:group-hover\:flex,
+.group:focus-visible .group-focus-visible\:flex,
+.group:active .group-active\:flex,
+.group:enabled .group-enabled\:flex,
+.group:disabled .group-disabled\:flex,
+.group:disabled:focus:hover .group-disabled\:group-focus\:group-hover\:flex,
+.group:disabled:focus:hover .group-disabled\:group-focus\:group-hover\:first\:flex:first-child,
+.peer:first-child ~ .peer-first\:flex,
+.peer:last-child ~ .peer-last\:flex,
+.peer:only-child ~ .peer-only\:flex,
+.peer:nth-child(odd) ~ .peer-odd\:flex,
+.peer:nth-child(2n) ~ .peer-even\:flex,
+.peer:first-of-type ~ .peer-first-of-type\:flex,
+.peer:last-of-type ~ .peer-last-of-type\:flex,
+.peer:only-of-type ~ .peer-only-of-type\:flex,
+.peer:visited ~ .peer-visited\:flex,
+.peer:target ~ .peer-target\:flex,
+.peer[open] ~ .peer-open\:flex,
+.peer:default ~ .peer-default\:flex,
+.peer:checked ~ .peer-checked\:flex,
+.peer:indeterminate ~ .peer-indeterminate\:flex,
+.peer:placeholder-shown ~ .peer-placeholder-shown\:flex,
+.peer:-webkit-autofill ~ .peer-autofill\:flex,
+.peer:optional ~ .peer-optional\:flex,
+.peer:required ~ .peer-required\:flex,
+.peer:valid ~ .peer-valid\:flex,
+.peer:invalid ~ .peer-invalid\:flex,
+.peer:in-range ~ .peer-in-range\:flex,
+.peer:out-of-range ~ .peer-out-of-range\:flex,
+.peer:read-only ~ .peer-read-only\:flex,
+.peer:empty ~ .peer-empty\:flex,
+.peer:focus-within ~ .peer-focus-within\:flex,
+.peer:hover ~ .peer-hover\:flex,
+.peer:focus ~ .peer-focus\:flex,
+.peer:focus:hover ~ .peer-focus\:peer-hover\:flex,
+.peer:focus-visible ~ .peer-focus-visible\:flex,
+.peer:active ~ .peer-active\:flex,
+.peer:enabled ~ .peer-enabled\:flex,
+.peer:disabled ~ .peer-disabled\:flex,
+.peer:disabled:focus:hover ~ .peer-disabled\:peer-focus\:peer-hover\:flex,
+.peer:disabled:focus:hover ~ .peer-disabled\:peer-focus\:peer-hover\:first\:flex:first-child,
+:-webkit-any([dir='ltr'] .ltr\:flex),
+:-webkit-any([dir='rtl'] .rtl\:flex) {
+ display: flex;
}
-.focus\:shadow-md:focus,
-.focus\:hover\:shadow-md:hover:focus,
-.focus-visible\:shadow-md:focus-visible,
-.active\:shadow-md:active,
-.enabled\:shadow-md:enabled,
-.disabled\:shadow-md:disabled,
-.group:first-child .group-first\:shadow-md,
-.group:last-child .group-last\:shadow-md,
-.group:only-child .group-only\:shadow-md,
-.group:nth-child(2n + 1) .group-odd\:shadow-md,
-.group:nth-child(2n) .group-even\:shadow-md,
-.group:first-of-type .group-first-of-type\:shadow-md,
-.group:last-of-type .group-last-of-type\:shadow-md,
-.group:only-of-type .group-only-of-type\:shadow-md,
-.group:visited .group-visited\:shadow-md,
-.group:target .group-target\:shadow-md {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
-}
-.group[open] .group-open\:bg-red-200 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 202 202 / var(--tw-bg-opacity));
-}
-.group:default .group-default\:shadow-md,
-.group:checked .group-checked\:shadow-md,
-.group:indeterminate .group-indeterminate\:shadow-md,
-.group:placeholder-shown .group-placeholder-shown\:shadow-md,
-.group:autofill .group-autofill\:shadow-md,
-.group:optional .group-optional\:shadow-md,
-.group:required .group-required\:shadow-md,
-.group:valid .group-valid\:shadow-md,
-.group:invalid .group-invalid\:shadow-md,
-.group:in-range .group-in-range\:shadow-md,
-.group:out-of-range .group-out-of-range\:shadow-md,
-.group:read-only .group-read-only\:shadow-md,
-.group:empty .group-empty\:shadow-md,
-.group:focus-within .group-focus-within\:shadow-md,
-.group:hover .group-hover\:shadow-md {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
-}
-.group[open]:hover .group-open\:group-hover\:space-x-2 > :not([hidden]) ~ :not([hidden]) {
- --tw-space-x-reverse: 0;
- margin-right: calc(0.5rem * var(--tw-space-x-reverse));
- margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
-}
-.group:focus .group-focus\:shadow-md {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
-}
-.group[open]:focus .group-open\:group-focus\:bg-red-200 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 202 202 / var(--tw-bg-opacity));
-}
-.group:focus:hover .group-focus\:group-hover\:shadow-md,
-.group:focus-visible .group-focus-visible\:shadow-md,
-.group:active .group-active\:shadow-md,
-.group:enabled .group-enabled\:shadow-md,
-.group:disabled .group-disabled\:shadow-md,
-.group:disabled:focus:hover .group-disabled\:group-focus\:group-hover\:shadow-md,
-.group:disabled:focus:hover .group-disabled\:group-focus\:group-hover\:first\:shadow-md:first-child,
-.peer:first-child ~ .peer-first\:shadow-md,
-.peer:last-child ~ .peer-last\:shadow-md,
-.peer:only-child ~ .peer-only\:shadow-md,
-.peer:nth-child(2n + 1) ~ .peer-odd\:shadow-md,
-.peer:nth-child(2n) ~ .peer-even\:shadow-md,
-.peer:first-of-type ~ .peer-first-of-type\:shadow-md,
-.peer:last-of-type ~ .peer-last-of-type\:shadow-md,
-.peer:only-of-type ~ .peer-only-of-type\:shadow-md,
-.peer:visited ~ .peer-visited\:shadow-md,
-.peer:target ~ .peer-target\:shadow-md {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
-}
-.peer[open] ~ .peer-open\:bg-red-200 {
- --tw-bg-opacity: 1;
- background-color: rgb(254 202 202 / var(--tw-bg-opacity));
-}
-.peer:default ~ .peer-default\:shadow-md,
-.peer:checked ~ .peer-checked\:shadow-md,
-.peer:indeterminate ~ .peer-indeterminate\:shadow-md,
-.peer:placeholder-shown ~ .peer-placeholder-shown\:shadow-md,
-.peer:autofill ~ .peer-autofill\:shadow-md,
-.peer:optional ~ .peer-optional\:shadow-md,
-.peer:required ~ .peer-required\:shadow-md,
-.peer:valid ~ .peer-valid\:shadow-md,
-.peer:invalid ~ .peer-invalid\:shadow-md,
-.peer:in-range ~ .peer-in-range\:shadow-md,
-.peer:out-of-range ~ .peer-out-of-range\:shadow-md,
-.peer:read-only ~ .peer-read-only\:shadow-md,
-.peer:empty ~ .peer-empty\:shadow-md,
-.peer:focus-within ~ .peer-focus-within\:shadow-md,
-.peer:hover ~ .peer-hover\:shadow-md,
-.peer:focus ~ .peer-focus\:shadow-md,
-.peer:focus:hover ~ .peer-focus\:peer-hover\:shadow-md,
-.peer:focus-visible ~ .peer-focus-visible\:shadow-md,
-.peer:active ~ .peer-active\:shadow-md,
-.peer:enabled ~ .peer-enabled\:shadow-md,
-.peer:disabled ~ .peer-disabled\:shadow-md,
-.peer:disabled:focus:hover ~ .peer-disabled\:peer-focus\:peer-hover\:shadow-md,
-.peer:disabled:focus:hover ~ .peer-disabled\:peer-focus\:peer-hover\:first\:shadow-md:first-child,
-:is([dir='ltr'] .ltr\:shadow-md),
-:is([dir='rtl'] .rtl\:shadow-md) {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
+.active\:flex:active,
+.enabled\:flex:enabled,
+.disabled\:flex:disabled,
+.group:first-child .group-first\:flex,
+.group:last-child .group-last\:flex,
+.group:only-child .group-only\:flex,
+.group:nth-child(odd) .group-odd\:flex,
+.group:nth-child(2n) .group-even\:flex,
+.group:first-of-type .group-first-of-type\:flex,
+.group:last-of-type .group-last-of-type\:flex,
+.group:only-of-type .group-only-of-type\:flex,
+.group:visited .group-visited\:flex,
+.group:target .group-target\:flex,
+.group[open] .group-open\:flex,
+.group:default .group-default\:flex,
+.group:checked .group-checked\:flex,
+.group:indeterminate .group-indeterminate\:flex,
+.group:placeholder-shown .group-placeholder-shown\:flex,
+.group:autofill .group-autofill\:flex,
+.group:optional .group-optional\:flex,
+.group:required .group-required\:flex,
+.group:valid .group-valid\:flex,
+.group:invalid .group-invalid\:flex,
+.group:in-range .group-in-range\:flex,
+.group:out-of-range .group-out-of-range\:flex,
+.group:read-only .group-read-only\:flex,
+.group:empty .group-empty\:flex,
+.group:focus-within .group-focus-within\:flex,
+.group:hover .group-hover\:flex,
+.group[open]:hover .group-open\:group-hover\:flex,
+.group:focus .group-focus\:flex,
+.group[open]:focus .group-open\:group-focus\:flex,
+.group:focus:hover .group-focus\:group-hover\:flex,
+.group:focus-visible .group-focus-visible\:flex,
+.group:active .group-active\:flex,
+.group:enabled .group-enabled\:flex,
+.group:disabled .group-disabled\:flex,
+.group:disabled:focus:hover .group-disabled\:group-focus\:group-hover\:flex,
+.group:disabled:focus:hover .group-disabled\:group-focus\:group-hover\:first\:flex:first-child,
+.peer:first-child ~ .peer-first\:flex,
+.peer:last-child ~ .peer-last\:flex,
+.peer:only-child ~ .peer-only\:flex,
+.peer:nth-child(odd) ~ .peer-odd\:flex,
+.peer:nth-child(2n) ~ .peer-even\:flex,
+.peer:first-of-type ~ .peer-first-of-type\:flex,
+.peer:last-of-type ~ .peer-last-of-type\:flex,
+.peer:only-of-type ~ .peer-only-of-type\:flex,
+.peer:visited ~ .peer-visited\:flex,
+.peer:target ~ .peer-target\:flex,
+.peer[open] ~ .peer-open\:flex,
+.peer:default ~ .peer-default\:flex,
+.peer:checked ~ .peer-checked\:flex,
+.peer:indeterminate ~ .peer-indeterminate\:flex,
+.peer:placeholder-shown ~ .peer-placeholder-shown\:flex,
+.peer:autofill ~ .peer-autofill\:flex,
+.peer:optional ~ .peer-optional\:flex,
+.peer:required ~ .peer-required\:flex,
+.peer:valid ~ .peer-valid\:flex,
+.peer:invalid ~ .peer-invalid\:flex,
+.peer:in-range ~ .peer-in-range\:flex,
+.peer:out-of-range ~ .peer-out-of-range\:flex,
+.peer:read-only ~ .peer-read-only\:flex,
+.peer:empty ~ .peer-empty\:flex,
+.peer:focus-within ~ .peer-focus-within\:flex,
+.peer:hover ~ .peer-hover\:flex,
+.peer:focus ~ .peer-focus\:flex,
+.peer:focus:hover ~ .peer-focus\:peer-hover\:flex,
+.peer:focus-visible ~ .peer-focus-visible\:flex,
+.peer:active ~ .peer-active\:flex,
+.peer:enabled ~ .peer-enabled\:flex,
+.peer:disabled ~ .peer-disabled\:flex,
+.peer:disabled:focus:hover ~ .peer-disabled\:peer-focus\:peer-hover\:flex,
+.peer:disabled:focus:hover ~ .peer-disabled\:peer-focus\:peer-hover\:first\:flex:first-child,
+:is([dir='ltr'] .ltr\:flex),
+:is([dir='rtl'] .rtl\:flex) {
+ display: flex;
}
@media (prefers-reduced-motion: no-preference) {
- .motion-safe\:shadow-md {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
- 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
+ .motion-safe\:flex {
+ display: flex;
}
}
@media (prefers-reduced-motion: reduce) {
- .motion-reduce\:shadow-md {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
- 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
+ .motion-reduce\:flex {
+ display: flex;
}
}
@media (prefers-contrast: more) {
- .contrast-more\:bg-yellow-300 {
- --tw-bg-opacity: 1;
- background-color: rgb(253 224 71 / var(--tw-bg-opacity));
+ .contrast-more\:flex {
+ display: flex;
}
}
@media (prefers-contrast: less) {
- .contrast-less\:bg-yellow-300 {
- --tw-bg-opacity: 1;
- background-color: rgb(253 224 71 / var(--tw-bg-opacity));
+ .contrast-less\:flex {
+ display: flex;
}
}
-:is(.dark .dark\:shadow-md),
-:is(.dark .group:disabled:focus:hover .dark\:group-disabled\:group-focus\:group-hover\:shadow-md),
-:is(.dark .peer:disabled:focus:hover ~ .dark\:peer-disabled\:peer-focus\:peer-hover\:shadow-md) {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
+:is(.dark .dark\:flex),
+:is(.dark .group:disabled:focus:hover .dark\:group-disabled\:group-focus\:group-hover\:flex),
+:is(.dark .peer:disabled:focus:hover ~ .dark\:peer-disabled\:peer-focus\:peer-hover\:flex) {
+ display: flex;
}
@media print {
- .print\:bg-yellow-300 {
- --tw-bg-opacity: 1;
- background-color: rgb(253 224 71 / var(--tw-bg-opacity));
+ .print\:flex {
+ display: flex;
}
}
@media (min-width: 640px) {
- .sm\:shadow-md,
- .sm\:active\:shadow-md:active {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
- 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
+ .sm\:flex,
+ .sm\:active\:flex:active {
+ display: flex;
}
}
@media (min-width: 768px) {
- .md\:shadow-md,
- .group:focus .md\:group-focus\:shadow-md {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
- 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
+ .md\:flex,
+ .group:focus .md\:group-focus\:flex {
+ display: flex;
}
}
@media (min-width: 1024px) {
- @keyframes spin {
- to {
- transform: rotate(360deg);
- }
- }
- .lg\:animate-spin {
- animation: 1s linear infinite spin;
- }
- .lg\:shadow-md,
- :is(.dark .lg\:dark\:shadow-md) {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
- 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
+ .lg\:flex,
+ :is(.dark .lg\:dark\:flex) {
+ display: flex;
}
}
@media (min-width: 1280px) {
- .xl\:shadow-md,
- :is(.dark .xl\:dark\:disabled\:shadow-md:disabled) {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
- 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
+ .xl\:flex,
+ :is(.dark .xl\:dark\:disabled\:flex:disabled) {
+ display: flex;
}
}
@media (min-width: 1536px) {
- .\32 xl\:shadow-md {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
- 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
+ .\32 xl\:flex {
+ display: flex;
}
@media (prefers-reduced-motion: no-preference) {
- :is(.dark .\32 xl\:dark\:motion-safe\:focus-within\:shadow-md:focus-within) {
- --tw-shadow: 0 4px 6px -1px #0000001a, 0 2px 4px -2px #0000001a;
- --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color),
- 0 2px 4px -2px var(--tw-shadow-color);
- box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000),
- var(--tw-shadow);
+ :is(.dark .\32 xl\:dark\:motion-safe\:focus-within\:flex:focus-within) {
+ display: flex;
}
}
}
@media (orientation: portrait) {
- .portrait\:bg-yellow-300 {
- --tw-bg-opacity: 1;
- background-color: rgb(253 224 71 / var(--tw-bg-opacity));
+ .portrait\:flex {
+ display: flex;
}
}
@media (orientation: landscape) {
- .landscape\:bg-yellow-300 {
- --tw-bg-opacity: 1;
- background-color: rgb(253 224 71 / var(--tw-bg-opacity));
+ .landscape\:flex {
+ display: flex;
}
}
diff --git a/tests/variants.test.html b/tests/variants.test.html
index bd40b625f..a9b2c2f16 100644
--- a/tests/variants.test.html
+++ b/tests/variants.test.html
@@ -1,166 +1,166 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
+
+
diff --git a/tests/variants.test.js b/tests/variants.test.js
index da62419d4..f2a2de075 100644
--- a/tests/variants.test.js
+++ b/tests/variants.test.js
@@ -1,414 +1,89 @@
import fs from 'fs'
import path from 'path'
import postcss from 'postcss'
-import { crosscheck, run, html, css, defaults } from './util/run'
+import { run, html, css, defaults } from './util/run'
-crosscheck(({ stable, oxide }) => {
- test('variants', () => {
- let config = {
- darkMode: 'class',
- content: [path.resolve(__dirname, './variants.test.html')],
- corePlugins: { preflight: false },
- }
+test('variants', () => {
+ let config = {
+ darkMode: 'class',
+ content: [path.resolve(__dirname, './variants.test.html')],
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
+ let input = css`
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+ `
- return run(input, config).then((result) => {
- stable
- .expect(result.css)
- .toMatchFormattedCss(
- fs.readFileSync(path.resolve(__dirname, './variants.test.css'), 'utf8')
- )
- oxide
- .expect(result.css)
- .toMatchFormattedCss(
- fs.readFileSync(path.resolve(__dirname, './variants.oxide.test.css'), 'utf8')
- )
- })
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(
+ fs.readFileSync(path.resolve(__dirname, './variants.test.css'), 'utf8')
+ )
})
+})
- test('order matters and produces different behaviour', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
- `,
- },
- ],
- }
+test('order matters and produces different behaviour', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+ `,
+ },
+ ],
+ }
- return run('@tailwind utilities', config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- .file\:hover\:bg-pink-600:hover::file-selector-button {
- --tw-bg-opacity: 1;
- background-color: rgb(219 39 119 / var(--tw-bg-opacity));
- }
- .hover\:file\:bg-pink-600::file-selector-button:hover {
- --tw-bg-opacity: 1;
- background-color: rgb(219 39 119 / var(--tw-bg-opacity));
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- .file\:hover\:bg-pink-600:hover::file-selector-button {
- background-color: #db2777;
- }
- .hover\:file\:bg-pink-600::file-selector-button:hover {
- background-color: #db2777;
- }
- `)
- })
- })
-
- describe('custom advanced variants', () => {
- test('prose-headings usage on its own', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addVariant }) {
- addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)')
- },
- ],
- }
-
- return run('@tailwind components;@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- :where(.prose-headings\:text-center) :is(h1, h2, h3, h4) {
- text-align: center;
- }
- `)
- })
- })
-
- test('prose-headings with another "simple" variant', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
- `,
- },
- ],
- plugins: [
- function ({ addVariant }) {
- addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)')
- },
- ],
- }
-
- return run('@tailwind components;@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- :where(.hover\:prose-headings\:text-center) :is(h1, h2, h3, h4):hover,
- :where(.prose-headings\:hover\:text-center:hover) :is(h1, h2, h3, h4) {
- text-align: center;
- }
- `)
- })
- })
-
- test('prose-headings with another "complex" variant', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
- `,
- },
- ],
- plugins: [
- function ({ addVariant }) {
- addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)')
- },
- ],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .group:hover :where(.group-hover\:prose-headings\:text-center) :is(h1, h2, h3, h4),
- :where(.group:hover .prose-headings\:group-hover\:text-center) :is(h1, h2, h3, h4) {
- text-align: center;
- }
- `)
- })
- })
-
- test('using variants with multi-class selectors', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addVariant, addComponents }) {
- addComponents({
- '.parent .child': {
- foo: 'bar',
- },
- })
- addVariant('screen', '@media screen')
- },
- ],
- }
-
- return run('@tailwind components;@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- @media screen {
- .screen\:parent .child,
- .parent .screen\:child {
- foo: bar;
- }
- }
- `)
- })
- })
-
- test('using multiple classNames in your custom variant', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addVariant }) {
- addVariant('my-variant', '&:where(.one, .two, .three)')
- },
- ],
- }
-
- let input = css`
- @tailwind components;
- @tailwind utilities;
-
- @layer components {
- .test {
- @apply my-variant:italic;
- }
- }
- `
-
- return run(input, config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .test:where(.one, .two, .three) {
- font-style: italic;
- }
- .my-variant\:underline:where(.one, .two, .three) {
- text-decoration-line: underline;
- }
- `)
- })
- })
-
- test('variant format string must include at-rule or & (1)', async () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addVariant }) {
- addVariant('wtf-bbq', 'lol')
- },
- ],
- }
-
- await expect(run('@tailwind components;@tailwind utilities', config)).rejects.toThrowError(
- "Your custom variant `wtf-bbq` has an invalid format string. Make sure it's an at-rule or contains a `&` placeholder."
- )
- })
-
- test('variant format string must include at-rule or & (2)', async () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addVariant }) {
- addVariant('wtf-bbq', () => 'lol')
- },
- ],
- }
-
- await expect(run('@tailwind components;@tailwind utilities', config)).rejects.toThrowError(
- "Your custom variant `wtf-bbq` has an invalid format string. Make sure it's an at-rule or contains a `&` placeholder."
- )
- })
- })
-
- test('stacked peer variants', async () => {
- let config = {
- content: [{ raw: 'peer-disabled:peer-focus:peer-hover:border-blue-500' }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
- `
-
- let result = await run(input, config)
- stable.expect(result.css).toIncludeCss(css`
- .peer:disabled:focus:hover ~ .peer-disabled\:peer-focus\:peer-hover\:border-blue-500 {
- --tw-border-opacity: 1;
- border-color: rgb(59 130 246 / var(--tw-border-opacity));
- }
- `)
- oxide.expect(result.css).toIncludeCss(css`
- .peer:disabled:focus:hover ~ .peer-disabled\:peer-focus\:peer-hover\:border-blue-500 {
- border-color: #3b82f6;
- }
- `)
- })
-
- it('should properly handle keyframes with multiple variants', async () => {
- let config = {
- content: [
- {
- raw: 'animate-spin hover:animate-spin focus:animate-spin hover:animate-bounce focus:animate-bounce',
- },
- ],
- }
-
- let input = css`
- @tailwind components;
- @tailwind utilities;
- `
-
- let result = await run(input, config)
+ return run('@tailwind utilities', config).then((result) => {
expect(result.css).toMatchFormattedCss(css`
- @keyframes spin {
- to {
- transform: rotate(360deg);
- }
+ .file\:hover\:\[--value\:2\]:hover::-webkit-file-upload-button {
+ --value: 2;
}
- .animate-spin {
- animation: 1s linear infinite spin;
+ .file\:hover\:\[--value\:2\]:hover::file-selector-button {
+ --value: 2;
}
- @keyframes bounce {
- 0%,
- 100% {
- animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
- transform: translateY(-25%);
- }
- 50% {
- animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
- transform: none;
- }
+ .hover\:file\:\[--value\:1\]::-webkit-file-upload-button:hover {
+ --value: 1;
}
- .hover\:animate-bounce:hover {
- animation: 1s infinite bounce;
- }
- .hover\:animate-spin:hover {
- animation: 1s linear infinite spin;
- }
- .focus\:animate-bounce:focus {
- animation: 1s infinite bounce;
- }
- .focus\:animate-spin:focus {
- animation: 1s linear infinite spin;
+ .hover\:file\:\[--value\:1\]::file-selector-button:hover {
+ --value: 1;
}
`)
})
+})
- test('custom addVariant with more complex media query params', () => {
+describe('custom advanced variants', () => {
+ test('prose-headings usage on its own', () => {
let config = {
content: [
{
- raw: html`
`,
+ raw: html`
`,
},
],
plugins: [
function ({ addVariant }) {
- addVariant('magic', '@media screen and (max-width: 600px)')
+ addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)')
},
],
}
return run('@tailwind components;@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
- @media screen and (max-width: 600px) {
- .magic\:text-center {
- text-align: center;
- }
- }
- `)
- })
- })
-
- test('custom addVariant with nested media & format shorthand', () => {
- let config = {
- content: [
- {
- raw: html`
`,
- },
- ],
- plugins: [
- function ({ addVariant }) {
- addVariant('magic', '@supports (hover: hover) { @media print { &:disabled } }')
- },
- ],
- }
-
- return run('@tailwind components;@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- @supports (hover: hover) {
- @media print {
- .magic\:text-center:disabled {
- text-align: center;
- }
- }
- }
- `)
- })
- })
-
- test('before and after variants are a bit special, and forced to the end', () => {
- let config = {
- content: [
- {
- raw: html`
-
-
- `,
- },
- ],
- plugins: [],
- }
-
- return run('@tailwind components;@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .before\:hover\:text-center:hover:before,
- .hover\:before\:text-center:hover:before {
- content: var(--tw-content);
+ :where(.prose-headings\:text-center) :is(h1, h2, h3, h4) {
text-align: center;
}
`)
})
})
- test('before and after variants are a bit special, and forced to the end (2)', () => {
+ test('prose-headings with another "simple" variant', () => {
let config = {
content: [
{
raw: html`
-
-
+
+
`,
},
],
@@ -421,759 +96,1049 @@ crosscheck(({ stable, oxide }) => {
return run('@tailwind components;@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
- :where(.before\:prose-headings\:text-center) :is(h1, h2, h3, h4):before,
- :where(.prose-headings\:before\:text-center) :is(h1, h2, h3, h4):before {
- content: var(--tw-content);
+ :where(.hover\:prose-headings\:text-center) :is(h1, h2, h3, h4):hover,
+ :where(.prose-headings\:hover\:text-center:hover) :is(h1, h2, h3, h4) {
text-align: center;
}
`)
})
})
- test('returning non-strings and non-selectors in addVariant', () => {
- /** @type {import('../types/config').Config} */
+ test('prose-headings with another "complex" variant', () => {
let config = {
content: [
{
raw: html`
-
-
+
+
`,
},
],
plugins: [
- function ({ addVariant, e }) {
- addVariant('peer-aria-expanded', ({ modifySelectors, separator }) =>
- // Returning anything other string | string[] | undefined here is not supported
- // But we're trying to be lenient here and just throw it out
- modifySelectors(
- ({ className }) =>
- `.peer[aria-expanded="true"] ~ .${e(`peer-aria-expanded${separator}${className}`)}`
- )
- )
+ function ({ addVariant }) {
+ addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)')
+ },
+ ],
+ }
- addVariant('peer-aria-expanded-2', ({ modifySelectors, separator }) => {
- let nodes = modifySelectors(
- ({ className }) => `.${e(`peer-aria-expanded-2${separator}${className}`)}`
- )
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .group:hover :where(.group-hover\:prose-headings\:text-center) :is(h1, h2, h3, h4),
+ :where(.group:hover .prose-headings\:group-hover\:text-center) :is(h1, h2, h3, h4) {
+ text-align: center;
+ }
+ `)
+ })
+ })
- return [
- // Returning anything other than strings here is not supported
- // But we're trying to be lenient here and just throw it out
- nodes,
- '.peer[aria-expanded="false"] ~ &',
- ]
+ test('using variants with multi-class selectors', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function ({ addVariant, addComponents }) {
+ addComponents({
+ '.parent .child': {
+ foo: 'bar',
+ },
})
+ addVariant('screen', '@media screen')
},
],
}
return run('@tailwind components;@tailwind utilities', config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
- .peer[aria-expanded='true'] ~ .peer-aria-expanded\:text-center,
- .peer[aria-expanded='false'] ~ .peer-aria-expanded-2\:text-center {
- text-align: center;
+ @media screen {
+ .screen\:parent .child,
+ .parent .screen\:child {
+ foo: bar;
+ }
}
`)
})
})
- it('should not generate variants of user css if it is not inside a layer', () => {
+ test('using multiple classNames in your custom variant', () => {
let config = {
- content: [{ raw: html`
` }],
- plugins: [],
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function ({ addVariant }) {
+ addVariant('my-variant', '&:where(.one, .two, .three)')
+ },
+ ],
}
let input = css`
@tailwind components;
@tailwind utilities;
- .foo {
- color: red;
+ @layer components {
+ .test {
+ @apply my-variant:italic;
+ }
}
`
return run(input, config).then((result) => {
return expect(result.css).toMatchFormattedCss(css`
- .foo {
- color: red;
+ .test:where(.one, .two, .three) {
+ font-style: italic;
}
- `)
- })
- })
-
- it('should be possible to use responsive modifiers that are defined with special characters', () => {
- let config = {
- content: [{ raw: html`
` }],
- theme: {
- screens: {
- ' {
- return expect(result.css).toMatchFormattedCss(css`
- @media (max-width: 399px) {
- .\ {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- return run('@tailwind base', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(
- css`
- ${defaults}
- `
- )
- })
- })
-
- it('variants for components should not be produced in a file without a components layer', () => {
- let config = {
- content: [{ raw: html`
` }],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 640px) {
- .sm\:underline {
- text-decoration-line: underline;
- }
- }
- `)
- })
- })
-
- it('variants for utilities should not be produced in a file without a utilities layer', () => {
- let config = {
- content: [{ raw: html`
` }],
- }
-
- return run('@tailwind components', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 640px) {
- .sm\:container {
- width: 100%;
- }
- @media (min-width: 640px) {
- .sm\:container {
- max-width: 640px;
- }
- }
- @media (min-width: 768px) {
- .sm\:container {
- max-width: 768px;
- }
- }
- @media (min-width: 1024px) {
- .sm\:container {
- max-width: 1024px;
- }
- }
- @media (min-width: 1280px) {
- .sm\:container {
- max-width: 1280px;
- }
- }
- @media (min-width: 1536px) {
- .sm\:container {
- max-width: 1536px;
- }
- }
- }
- `)
- })
- })
-
- test('The visited variant removes opacity support', () => {
- let config = {
- content: [
- {
- raw: html`
- Look, it's a link!
- `,
- },
- ],
- plugins: [],
- }
-
- return run('@tailwind utilities', config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- .visited\:border-red-500:visited {
- border-color: #ef4444;
- }
- .visited\:bg-red-500:visited {
- background-color: #ef4444;
- }
- .visited\:text-red-500:visited {
- color: #ef4444;
- }
- `)
- })
- })
-
- it('appends variants to the correct place when using postcss documents', () => {
- let config = {
- content: [{ raw: html`
` }],
- plugins: [],
- corePlugins: { preflight: false },
- }
-
- const doc = postcss.document()
- doc.append(postcss.parse(`a {}`))
- doc.append(postcss.parse(`@tailwind base`))
- doc.append(postcss.parse(`@tailwind utilities`))
- doc.append(postcss.parse(`b {}`))
-
- const result = doc.toResult()
-
- return run(result, config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- ${defaults}
- .underline {
+ .my-variant\:underline:where(.one, .two, .three) {
text-decoration-line: underline;
}
- @media (min-width: 640px) {
- .sm\:underline {
- text-decoration-line: underline;
- }
- }
`)
})
})
- it('variants support multiple, grouped selectors (html)', () => {
- let config = {
- content: [{ raw: html`
` }],
- plugins: [],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- @layer utilities {
- .base1 .foo,
- .base1 .bar {
- color: red;
- }
-
- .base2 .bar .base2-foo {
- color: red;
- }
- }
- `
-
- return run(input, config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 640px) {
- .sm\:base1 .foo,
- .sm\:base1 .bar,
- .sm\:base2 .bar .base2-foo {
- color: red;
- }
- }
- `)
- })
- })
-
- it('variants support multiple, grouped selectors (apply)', () => {
- let config = {
- content: [{ raw: html`
` }],
- plugins: [],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- @layer utilities {
- .base .foo,
- .base .bar {
- color: red;
- }
- }
- .baz {
- @apply sm:base;
- }
- `
-
- return run(input, config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 640px) {
- .baz .foo,
- .baz .bar {
- color: red;
- }
- }
- `)
- })
- })
-
- it('variants only picks the used selectors in a group (html)', () => {
- let config = {
- content: [{ raw: html`
` }],
- plugins: [],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- @layer utilities {
- .a,
- .b {
- color: red;
- }
- }
- `
-
- return run(input, config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 640px) {
- .sm\:b {
- color: red;
- }
- }
- `)
- })
- })
-
- it('variants only picks the used selectors in a group (apply)', () => {
- let config = {
- content: [{ raw: html`
` }],
- plugins: [],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- @layer utilities {
- .a,
- .b {
- color: red;
- }
- }
- .baz {
- @apply sm:b;
- }
- `
-
- return run(input, config).then((result) => {
- return expect(result.css).toMatchFormattedCss(css`
- @media (min-width: 640px) {
- .baz {
- color: red;
- }
- }
- `)
- })
- })
-
- test('hoverOnlyWhenSupported adds hover and pointer media features by default', () => {
- let config = {
- future: {
- hoverOnlyWhenSupported: true,
- },
- 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) and (pointer: fine) {
- .hover\:underline:hover,
- .group:hover .group-hover\:underline,
- .peer:hover ~ .peer-hover\:underline {
- text-decoration-line: underline;
- }
- }
- `)
- })
- })
-
- test('multi-class utilities handle selector-mutating variants correctly', () => {
+ test('variant format string must include at-rule or & (1)', async () => {
let config = {
content: [
{
- raw: html`
`,
- },
- {
- raw: html`
`,
+ raw: html`
`,
},
],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- @layer utilities {
- .foo.bar.baz {
- color: red;
- }
- .foo1 .bar1 .baz1 {
- color: red;
- }
- }
- `
-
- // The second set of ::after cases (w/ descendant selectors)
- // are clearly "wrong" BUT you can't have a descendant of a
- // pseudo - element so the utilities `after:foo1` and
- // `after:bar1` are non-sensical so this is still
- // perfectly fine behavior
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- .after\:foo.bar.baz:after,
- .after\:bar.foo.baz:after,
- .after\:baz.foo.bar:after,
- .after\:foo1 .bar1 .baz1:after,
- .foo1 .after\:bar1 .baz1:after,
- .foo1 .bar1 .after\:baz1:after {
- content: var(--tw-content);
- color: red;
- }
- .hover\:foo:hover.bar.baz,
- .hover\:bar:hover.foo.baz,
- .hover\:baz:hover.foo.bar,
- .hover\:foo1:hover .bar1 .baz1,
- .foo1 .hover\:bar1:hover .baz1,
- .foo1 .bar1 .hover\:baz1:hover,
- .group:hover .group-hover\:foo.bar.baz,
- .group:hover .group-hover\:bar.foo.baz,
- .group:hover .group-hover\:baz.foo.bar,
- .group:hover .group-hover\:foo1 .bar1 .baz1,
- .foo1 .group:hover .group-hover\:bar1 .baz1,
- .foo1 .bar1 .group:hover .group-hover\:baz1,
- .peer:checked ~ .peer-checked\:foo.bar.baz,
- .peer:checked ~ .peer-checked\:bar.foo.baz,
- .peer:checked ~ .peer-checked\:baz.foo.bar,
- .peer:checked ~ .peer-checked\:foo1 .bar1 .baz1,
- .foo1 .peer:checked ~ .peer-checked\:bar1 .baz1,
- .foo1 .bar1 .peer:checked ~ .peer-checked\:baz1 {
- color: red;
- }
- `)
- })
- })
-
- test('class inside pseudo-class function :has', () => {
- let config = {
- content: [
- { raw: html`
` },
- { raw: html`
` },
- { raw: html`
` },
- ],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- @layer utilities {
- :where(.foo) {
- color: red;
- }
- :is(.foo, .bar, .baz) {
- color: orange;
- }
- :is(.foo) {
- color: yellow;
- }
- html:has(.foo) {
- color: green;
- }
- }
- `
-
- return run(input, config).then((result) => {
- expect(result.css).toMatchFormattedCss(css`
- :where(.foo) {
- color: red;
- }
- :is(.foo, .bar, .baz) {
- color: orange;
- }
- .foo {
- color: #ff0;
- }
- html:has(.foo) {
- color: green;
- }
- :where(.hover\:foo:hover) {
- color: red;
- }
- :is(.hover\:foo:hover, .bar, .baz),
- :is(.foo, .hover\:bar:hover, .baz),
- :is(.foo, .bar, .hover\:baz:hover) {
- color: orange;
- }
- .hover\:foo:hover {
- color: #ff0;
- }
- html:has(.hover\:foo:hover) {
- color: green;
- }
- @media (min-width: 640px) {
- :where(.sm\:foo) {
- color: red;
- }
- :is(.sm\:foo, .bar, .baz),
- :is(.foo, .sm\:bar, .baz),
- :is(.foo, .bar, .sm\:baz) {
- color: orange;
- }
- .sm\:foo {
- color: #ff0;
- }
- html:has(.sm\:foo) {
- color: green;
- }
- }
- `)
- })
- })
-
- test('variant functions returning arrays should output correct results when nesting', async () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
plugins: [
- function ({ addUtilities, addVariant }) {
- addVariant('test', () => ['@media (test)'])
- addUtilities({
- '.foo': {
- display: 'grid',
- '> *': {
- 'grid-column': 'span 2',
- },
- },
- })
+ function ({ addVariant }) {
+ addVariant('wtf-bbq', 'lol')
},
],
}
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- @media (test) {
- .test\:foo {
- display: grid;
- }
- .test\:foo > * {
- grid-column: span 2;
- }
- }
- `)
+ await expect(run('@tailwind components;@tailwind utilities', config)).rejects.toThrowError(
+ "Your custom variant `wtf-bbq` has an invalid format string. Make sure it's an at-rule or contains a `&` placeholder."
+ )
})
- test('variants with slashes in them work', () => {
+ test('variant format string must include at-rule or & (2)', async () => {
let config = {
content: [
{
- raw: html` ar-1/10
`,
+ raw: html`
`,
},
],
- theme: {
- extend: {
- screens: {
- 'ar-1/10': { raw: '(min-aspect-ratio: 1/10)' },
- },
- },
- },
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- return run(input, config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- @media (min-aspect-ratio: 1 / 10) {
- .ar-1\/10\:text-red-500 {
- --tw-text-opacity: 1;
- color: rgb(239 68 68 / var(--tw-text-opacity));
- }
- }
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- @media (min-aspect-ratio: 1 / 10) {
- .ar-1\/10\:text-red-500 {
- color: #ef4444;
- }
- }
- `)
- })
- })
-
- test('variants with slashes support modifiers', () => {
- let config = {
- content: [
- {
- raw: html` ar-1/10
`,
- },
- ],
- corePlugins: { preflight: false },
plugins: [
- function ({ matchVariant }) {
- matchVariant(
- 'ar',
- (value, { modifier }) => {
- return [`@media (min-aspect-ratio: ${value}) and (foo: ${modifier})`]
- },
- { values: { '1/10': '1/10' } }
- )
+ function ({ addVariant }) {
+ addVariant('wtf-bbq', () => 'lol')
},
],
}
- let input = css`
- @tailwind utilities;
- `
+ await expect(run('@tailwind components;@tailwind utilities', config)).rejects.toThrowError(
+ "Your custom variant `wtf-bbq` has an invalid format string. Make sure it's an at-rule or contains a `&` placeholder."
+ )
+ })
+})
- return run(input, config).then((result) => {
- stable.expect(result.css).toMatchFormattedCss(css`
- @media (min-aspect-ratio: 1 / 10) and (foo: 20) {
- .ar-1\/10\/20\:text-red-500 {
- --tw-text-opacity: 1;
- color: rgb(239 68 68 / var(--tw-text-opacity));
- }
+test('stacked peer variants', async () => {
+ let config = {
+ content: [{ raw: 'peer-disabled:peer-focus:peer-hover:flex' }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind base;
+ @tailwind components;
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+ expect(result.css).toIncludeCss(css`
+ .peer:disabled:focus:hover ~ .peer-disabled\:peer-focus\:peer-hover\:flex {
+ display: flex;
+ }
+ `)
+})
+
+it('should properly handle keyframes with multiple variants', async () => {
+ let config = {
+ content: [
+ {
+ raw: 'animate-spin hover:animate-spin focus:animate-spin hover:animate-bounce focus:animate-bounce',
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind components;
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+ expect(result.css).toMatchFormattedCss(css`
+ @keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+ }
+ .animate-spin {
+ animation: 1s linear infinite spin;
+ }
+ @keyframes bounce {
+ 0%,
+ 100% {
+ animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
+ transform: translateY(-25%);
+ }
+ 50% {
+ animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
+ transform: none;
+ }
+ }
+ .hover\:animate-bounce:hover {
+ animation: 1s infinite bounce;
+ }
+ .hover\:animate-spin:hover {
+ animation: 1s linear infinite spin;
+ }
+ .focus\:animate-bounce:focus {
+ animation: 1s infinite bounce;
+ }
+ .focus\:animate-spin:focus {
+ animation: 1s linear infinite spin;
+ }
+ `)
+})
+
+test('custom addVariant with more complex media query params', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function ({ addVariant }) {
+ addVariant('magic', '@media screen and (max-width: 600px)')
+ },
+ ],
+ }
+
+ return run('@tailwind components;@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ @media screen and (max-width: 600px) {
+ .magic\:text-center {
+ text-align: center;
}
- `)
- oxide.expect(result.css).toMatchFormattedCss(css`
- @media (min-aspect-ratio: 1 / 10) and (foo: 20) {
- .ar-1\/10\/20\:text-red-500 {
- color: #ef4444;
- }
- }
- `)
- })
- })
-
- test('arbitrary variant selectors should not re-order scrollbar pseudo classes', async () => {
- let config = {
- content: [
- {
- raw: html`
-
-
-
-
-
-
-
- `,
- },
- ],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- .\[\&\:\:-webkit-resizer\:hover\]\:underline::-webkit-resizer:hover {
- text-decoration-line: underline;
- }
- .\[\&\:\:-webkit-scrollbar-button\:hover\]\:underline::-webkit-scrollbar-button:hover {
- text-decoration-line: underline;
- }
- .\[\&\:\:-webkit-scrollbar-corner\:hover\]\:underline::-webkit-scrollbar-corner:hover {
- text-decoration-line: underline;
- }
- .\[\&\:\:-webkit-scrollbar-thumb\:hover\]\:underline::-webkit-scrollbar-thumb:hover {
- text-decoration-line: underline;
- }
- .\[\&\:\:-webkit-scrollbar-track-piece\:hover\]\:underline::-webkit-scrollbar-track-piece:hover {
- text-decoration-line: underline;
- }
- .\[\&\:\:-webkit-scrollbar-track\:hover\]\:underline::-webkit-scrollbar-track:hover {
- text-decoration-line: underline;
- }
- .\[\&\:\:-webkit-scrollbar\:hover\]\:underline::-webkit-scrollbar:hover {
- text-decoration-line: underline;
- }
- `)
- })
-
- test('stacking dark and rtl variants', async () => {
- let config = {
- darkMode: 'class',
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- :is(.dark :is([dir='rtl'] .dark\:rtl\:italic)) {
- font-style: italic;
- }
- `)
- })
-
- test('stacking dark and rtl variants with pseudo elements', async () => {
- let config = {
- darkMode: 'class',
- content: [
- {
- raw: html`
`,
- },
- ],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- let result = await run(input, config)
-
- expect(result.css).toMatchFormattedCss(css`
- :is(.dark :is([dir='rtl'] .dark\:rtl\:placeholder\:italic))::placeholder {
- font-style: italic;
}
`)
})
})
+
+test('custom addVariant with nested media & format shorthand', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ plugins: [
+ function ({ addVariant }) {
+ addVariant('magic', '@supports (hover: hover) { @media print { &:disabled } }')
+ },
+ ],
+ }
+
+ return run('@tailwind components;@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ @supports (hover: hover) {
+ @media print {
+ .magic\:text-center:disabled {
+ text-align: center;
+ }
+ }
+ }
+ `)
+ })
+})
+
+test('before and after variants are a bit special, and forced to the end', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+ `,
+ },
+ ],
+ plugins: [],
+ }
+
+ return run('@tailwind components;@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .before\:hover\:text-center:hover:before,
+ .hover\:before\:text-center:hover:before {
+ content: var(--tw-content);
+ text-align: center;
+ }
+ `)
+ })
+})
+
+test('before and after variants are a bit special, and forced to the end (2)', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+ `,
+ },
+ ],
+ plugins: [
+ function ({ addVariant }) {
+ addVariant('prose-headings', ':where(&) :is(h1, h2, h3, h4)')
+ },
+ ],
+ }
+
+ return run('@tailwind components;@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ :where(.before\:prose-headings\:text-center) :is(h1, h2, h3, h4):before,
+ :where(.prose-headings\:before\:text-center) :is(h1, h2, h3, h4):before {
+ content: var(--tw-content);
+ text-align: center;
+ }
+ `)
+ })
+})
+
+test('returning non-strings and non-selectors in addVariant', () => {
+ /** @type {import('../types/config').Config} */
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+ `,
+ },
+ ],
+ plugins: [
+ function ({ addVariant, e }) {
+ addVariant('peer-aria-expanded', ({ modifySelectors, separator }) =>
+ // Returning anything other string | string[] | undefined here is not supported
+ // But we're trying to be lenient here and just throw it out
+ modifySelectors(
+ ({ className }) =>
+ `.peer[aria-expanded="true"] ~ .${e(`peer-aria-expanded${separator}${className}`)}`
+ )
+ )
+
+ addVariant('peer-aria-expanded-2', ({ modifySelectors, separator }) => {
+ let nodes = modifySelectors(
+ ({ className }) => `.${e(`peer-aria-expanded-2${separator}${className}`)}`
+ )
+
+ return [
+ // Returning anything other than strings here is not supported
+ // But we're trying to be lenient here and just throw it out
+ nodes,
+ '.peer[aria-expanded="false"] ~ &',
+ ]
+ })
+ },
+ ],
+ }
+
+ return run('@tailwind components;@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .peer[aria-expanded='true'] ~ .peer-aria-expanded\:text-center,
+ .peer[aria-expanded='false'] ~ .peer-aria-expanded-2\:text-center {
+ text-align: center;
+ }
+ `)
+ })
+})
+
+it('should not generate variants of user css if it is not inside a layer', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ plugins: [],
+ }
+
+ let input = css`
+ @tailwind components;
+ @tailwind utilities;
+
+ .foo {
+ color: red;
+ }
+ `
+
+ return run(input, config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .foo {
+ color: red;
+ }
+ `)
+ })
+})
+
+it('should be possible to use responsive modifiers that are defined with special characters', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ theme: {
+ screens: {
+ ' {
+ return expect(result.css).toMatchFormattedCss(css`
+ @media (max-width: 399px) {
+ .\ {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ return run('@tailwind base', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(
+ css`
+ ${defaults}
+ `
+ )
+ })
+})
+
+it('variants for components should not be produced in a file without a components layer', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 640px) {
+ .sm\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ `)
+ })
+})
+
+it('variants for utilities should not be produced in a file without a utilities layer', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ }
+
+ return run('@tailwind components', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 640px) {
+ .sm\:container {
+ width: 100%;
+ }
+ @media (min-width: 640px) {
+ .sm\:container {
+ max-width: 640px;
+ }
+ }
+ @media (min-width: 768px) {
+ .sm\:container {
+ max-width: 768px;
+ }
+ }
+ @media (min-width: 1024px) {
+ .sm\:container {
+ max-width: 1024px;
+ }
+ }
+ @media (min-width: 1280px) {
+ .sm\:container {
+ max-width: 1280px;
+ }
+ }
+ @media (min-width: 1536px) {
+ .sm\:container {
+ max-width: 1536px;
+ }
+ }
+ }
+ `)
+ })
+})
+
+test('The visited variant removes opacity support', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+ Look, it's a link!
+ `,
+ },
+ ],
+ plugins: [],
+ }
+
+ return run('@tailwind utilities', config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ .visited\:border-red-500:visited {
+ border-color: #ef4444;
+ }
+ .visited\:bg-red-500:visited {
+ background-color: #ef4444;
+ }
+ .visited\:text-red-500:visited {
+ color: #ef4444;
+ }
+ `)
+ })
+})
+
+it('appends variants to the correct place when using postcss documents', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ plugins: [],
+ corePlugins: { preflight: false },
+ }
+
+ const doc = postcss.document()
+ doc.append(postcss.parse(`a {}`))
+ doc.append(postcss.parse(`@tailwind base`))
+ doc.append(postcss.parse(`@tailwind utilities`))
+ doc.append(postcss.parse(`b {}`))
+
+ const result = doc.toResult()
+
+ return run(result, config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ ${defaults}
+ .underline {
+ text-decoration-line: underline;
+ }
+ @media (min-width: 640px) {
+ .sm\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ `)
+ })
+})
+
+it('variants support multiple, grouped selectors (html)', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ plugins: [],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ @layer utilities {
+ .base1 .foo,
+ .base1 .bar {
+ color: red;
+ }
+
+ .base2 .bar .base2-foo {
+ color: red;
+ }
+ }
+ `
+
+ return run(input, config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 640px) {
+ .sm\:base1 .foo,
+ .sm\:base1 .bar,
+ .sm\:base2 .bar .base2-foo {
+ color: red;
+ }
+ }
+ `)
+ })
+})
+
+it('variants support multiple, grouped selectors (apply)', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ plugins: [],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ @layer utilities {
+ .base .foo,
+ .base .bar {
+ color: red;
+ }
+ }
+ .baz {
+ @apply sm:base;
+ }
+ `
+
+ return run(input, config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 640px) {
+ .baz .foo,
+ .baz .bar {
+ color: red;
+ }
+ }
+ `)
+ })
+})
+
+it('variants only picks the used selectors in a group (html)', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ plugins: [],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ @layer utilities {
+ .a,
+ .b {
+ color: red;
+ }
+ }
+ `
+
+ return run(input, config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 640px) {
+ .sm\:b {
+ color: red;
+ }
+ }
+ `)
+ })
+})
+
+it('variants only picks the used selectors in a group (apply)', () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ plugins: [],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ @layer utilities {
+ .a,
+ .b {
+ color: red;
+ }
+ }
+ .baz {
+ @apply sm:b;
+ }
+ `
+
+ return run(input, config).then((result) => {
+ return expect(result.css).toMatchFormattedCss(css`
+ @media (min-width: 640px) {
+ .baz {
+ color: red;
+ }
+ }
+ `)
+ })
+})
+
+test('hoverOnlyWhenSupported adds hover and pointer media features by default', () => {
+ let config = {
+ future: {
+ hoverOnlyWhenSupported: true,
+ },
+ 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) and (pointer: fine) {
+ .hover\:underline:hover,
+ .group:hover .group-hover\:underline,
+ .peer:hover ~ .peer-hover\:underline {
+ text-decoration-line: underline;
+ }
+ }
+ `)
+ })
+})
+
+test('multi-class utilities handle selector-mutating variants correctly', () => {
+ let config = {
+ content: [
+ {
+ raw: html`
`,
+ },
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ @layer utilities {
+ .foo.bar.baz {
+ color: red;
+ }
+ .foo1 .bar1 .baz1 {
+ color: red;
+ }
+ }
+ `
+
+ // The second set of ::after cases (w/ descendant selectors)
+ // are clearly "wrong" BUT you can't have a descendant of a
+ // pseudo - element so the utilities `after:foo1` and
+ // `after:bar1` are non-sensical so this is still
+ // perfectly fine behavior
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ .after\:foo.bar.baz:after,
+ .after\:bar.foo.baz:after,
+ .after\:baz.foo.bar:after,
+ .after\:foo1 .bar1 .baz1:after,
+ .foo1 .after\:bar1 .baz1:after,
+ .foo1 .bar1 .after\:baz1:after {
+ content: var(--tw-content);
+ color: red;
+ }
+ .hover\:foo:hover.bar.baz,
+ .hover\:bar:hover.foo.baz,
+ .hover\:baz:hover.foo.bar,
+ .hover\:foo1:hover .bar1 .baz1,
+ .foo1 .hover\:bar1:hover .baz1,
+ .foo1 .bar1 .hover\:baz1:hover,
+ .group:hover .group-hover\:foo.bar.baz,
+ .group:hover .group-hover\:bar.foo.baz,
+ .group:hover .group-hover\:baz.foo.bar,
+ .group:hover .group-hover\:foo1 .bar1 .baz1,
+ .foo1 .group:hover .group-hover\:bar1 .baz1,
+ .foo1 .bar1 .group:hover .group-hover\:baz1,
+ .peer:checked ~ .peer-checked\:foo.bar.baz,
+ .peer:checked ~ .peer-checked\:bar.foo.baz,
+ .peer:checked ~ .peer-checked\:baz.foo.bar,
+ .peer:checked ~ .peer-checked\:foo1 .bar1 .baz1,
+ .foo1 .peer:checked ~ .peer-checked\:bar1 .baz1,
+ .foo1 .bar1 .peer:checked ~ .peer-checked\:baz1 {
+ color: red;
+ }
+ `)
+ })
+})
+
+test('class inside pseudo-class function :has', () => {
+ let config = {
+ content: [
+ { raw: html`
` },
+ { raw: html`
` },
+ { raw: html`
` },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ @layer utilities {
+ :where(.foo) {
+ color: red;
+ }
+ :is(.foo, .bar, .baz) {
+ color: orange;
+ }
+ :is(.foo) {
+ color: yellow;
+ }
+ html:has(.foo) {
+ color: green;
+ }
+ }
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ :where(.foo) {
+ color: red;
+ }
+ :is(.foo, .bar, .baz) {
+ color: orange;
+ }
+ .foo {
+ color: #ff0;
+ }
+ html:has(.foo) {
+ color: green;
+ }
+ :where(.hover\:foo:hover) {
+ color: red;
+ }
+ :is(.hover\:foo:hover, .bar, .baz),
+ :is(.foo, .hover\:bar:hover, .baz),
+ :is(.foo, .bar, .hover\:baz:hover) {
+ color: orange;
+ }
+ .hover\:foo:hover {
+ color: #ff0;
+ }
+ html:has(.hover\:foo:hover) {
+ color: green;
+ }
+ @media (min-width: 640px) {
+ :where(.sm\:foo) {
+ color: red;
+ }
+ :is(.sm\:foo, .bar, .baz),
+ :is(.foo, .sm\:bar, .baz),
+ :is(.foo, .bar, .sm\:baz) {
+ color: orange;
+ }
+ .sm\:foo {
+ color: #ff0;
+ }
+ html:has(.sm\:foo) {
+ color: green;
+ }
+ }
+ `)
+ })
+})
+
+test('variant functions returning arrays should output correct results when nesting', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ plugins: [
+ function ({ addUtilities, addVariant }) {
+ addVariant('test', () => ['@media (test)'])
+ addUtilities({
+ '.foo': {
+ display: 'grid',
+ '> *': {
+ 'grid-column': 'span 2',
+ },
+ },
+ })
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ @media (test) {
+ .test\:foo {
+ display: grid;
+ }
+ .test\:foo > * {
+ grid-column: span 2;
+ }
+ }
+ `)
+})
+
+test('variants with slashes in them work', () => {
+ let config = {
+ content: [
+ {
+ raw: html` ar-1/10
`,
+ },
+ ],
+ theme: {
+ extend: {
+ screens: {
+ 'ar-1/10': { raw: '(min-aspect-ratio: 1/10)' },
+ },
+ },
+ },
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-aspect-ratio: 1 / 10) {
+ .ar-1\/10\:flex {
+ display: flex;
+ }
+ }
+ `)
+ })
+})
+
+test('variants with slashes support modifiers', () => {
+ let config = {
+ content: [
+ {
+ raw: html` ar-1/10
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ plugins: [
+ function ({ matchVariant }) {
+ matchVariant(
+ 'ar',
+ (value, { modifier }) => {
+ return [`@media (min-aspect-ratio: ${value}) and (foo: ${modifier})`]
+ },
+ { values: { '1/10': '1/10' } }
+ )
+ },
+ ],
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ return run(input, config).then((result) => {
+ expect(result.css).toMatchFormattedCss(css`
+ @media (min-aspect-ratio: 1 / 10) and (foo: 20) {
+ .ar-1\/10\/20\:flex {
+ display: flex;
+ }
+ }
+ `)
+ })
+})
+
+test('arbitrary variant selectors should not re-order scrollbar pseudo classes', async () => {
+ let config = {
+ content: [
+ {
+ raw: html`
+
+
+
+
+
+
+
+ `,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ .\[\&\:\:-webkit-resizer\:hover\]\:underline::-webkit-resizer:hover {
+ text-decoration-line: underline;
+ }
+ .\[\&\:\:-webkit-scrollbar-button\:hover\]\:underline::-webkit-scrollbar-button:hover {
+ text-decoration-line: underline;
+ }
+ .\[\&\:\:-webkit-scrollbar-corner\:hover\]\:underline::-webkit-scrollbar-corner:hover {
+ text-decoration-line: underline;
+ }
+ .\[\&\:\:-webkit-scrollbar-thumb\:hover\]\:underline::-webkit-scrollbar-thumb:hover {
+ text-decoration-line: underline;
+ }
+ .\[\&\:\:-webkit-scrollbar-track-piece\:hover\]\:underline::-webkit-scrollbar-track-piece:hover {
+ text-decoration-line: underline;
+ }
+ .\[\&\:\:-webkit-scrollbar-track\:hover\]\:underline::-webkit-scrollbar-track:hover {
+ text-decoration-line: underline;
+ }
+ .\[\&\:\:-webkit-scrollbar\:hover\]\:underline::-webkit-scrollbar:hover {
+ text-decoration-line: underline;
+ }
+ `)
+})
+
+test('stacking dark and rtl variants', async () => {
+ let config = {
+ darkMode: 'class',
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ :is(.dark :is([dir='rtl'] .dark\:rtl\:italic)) {
+ font-style: italic;
+ }
+ `)
+})
+
+test('stacking dark and rtl variants with pseudo elements', async () => {
+ let config = {
+ darkMode: 'class',
+ content: [
+ {
+ raw: html`
`,
+ },
+ ],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ let result = await run(input, config)
+
+ expect(result.css).toMatchFormattedCss(css`
+ :is(.dark :is([dir='rtl'] .dark\:rtl\:placeholder\:italic))::placeholder {
+ font-style: italic;
+ }
+ `)
+})
diff --git a/tests/warnings.test.js b/tests/warnings.test.js
index e3ea94fe5..d74f1dd52 100644
--- a/tests/warnings.test.js
+++ b/tests/warnings.test.js
@@ -1,62 +1,61 @@
-import { crosscheck, run, html, css } from './util/run'
+import { run, html, css } from './util/run'
-crosscheck(() => {
- test('it warns when there is no content key', async () => {
- let config = {
- corePlugins: { preflight: false },
- }
+test('it warns when there is no content key', async () => {
+ let config = {
+ presets: [], // Prevents the default config from being merged in and therefore `content: ['auto']` is not used.
+ corePlugins: { preflight: false },
+ }
- let input = css`
- @tailwind base;
- `
+ let input = css`
+ @tailwind base;
+ `
- await run(input, config)
+ await run(input, config)
- expect().toHaveBeenWarnedWith(['content-problems'])
- })
-
- test('it warns when there is an empty content key', async () => {
- let config = {
- content: [],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind base;
- `
-
- await run(input, config)
-
- expect().toHaveBeenWarnedWith(['content-problems'])
- })
-
- test('it warns when there are no utilities generated', async () => {
- let config = {
- content: [{ raw: html`nothing here matching a utility` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- await run(input, config)
-
- expect().toHaveBeenWarnedWith(['content-problems'])
- })
-
- it('warnings are not thrown when only variant utilities are generated', async () => {
- let config = {
- content: [{ raw: html`
` }],
- corePlugins: { preflight: false },
- }
-
- let input = css`
- @tailwind utilities;
- `
-
- await run(input, config)
-
- expect().not.toHaveBeenWarned()
- })
+ expect().toHaveBeenWarnedWith(['content-problems'])
+})
+
+test('it warns when there is an empty content key', async () => {
+ let config = {
+ content: [],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind base;
+ `
+
+ await run(input, config)
+
+ expect().toHaveBeenWarnedWith(['content-problems'])
+})
+
+test('it warns when there are no utilities generated', async () => {
+ let config = {
+ content: [{ raw: html`nothing here matching a utility` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ await run(input, config)
+
+ expect().toHaveBeenWarnedWith(['content-problems'])
+})
+
+it('warnings are not thrown when only variant utilities are generated', async () => {
+ let config = {
+ content: [{ raw: html`
` }],
+ corePlugins: { preflight: false },
+ }
+
+ let input = css`
+ @tailwind utilities;
+ `
+
+ await run(input, config)
+
+ expect().not.toHaveBeenWarned()
})
diff --git a/tests/withAlphaVariable.test.js b/tests/withAlphaVariable.test.js
index 29d39c7aa..d543dfc6e 100644
--- a/tests/withAlphaVariable.test.js
+++ b/tests/withAlphaVariable.test.js
@@ -1,243 +1,240 @@
import withAlphaVariable from '../src/util/withAlphaVariable'
-import { crosscheck } from './util/run'
-crosscheck(() => {
- test('it adds the right custom property', () => {
- expect(
- withAlphaVariable({ color: '#ff0000', property: 'color', variable: '--tw-text-opacity' })
- ).toEqual({
- '--tw-text-opacity': '1',
- color: 'rgb(255 0 0 / var(--tw-text-opacity))',
- })
- expect(
- withAlphaVariable({
- color: 'hsl(240 100% 50%)',
- property: 'color',
- variable: '--tw-text-opacity',
- })
- ).toEqual({
- '--tw-text-opacity': '1',
- color: 'hsl(240 100% 50% / var(--tw-text-opacity))',
- })
+test('it adds the right custom property', () => {
+ expect(
+ withAlphaVariable({ color: '#ff0000', property: 'color', variable: '--tw-text-opacity' })
+ ).toEqual({
+ '--tw-text-opacity': '1',
+ color: 'rgb(255 0 0 / var(--tw-text-opacity))',
})
-
- test('it ignores colors that cannot be parsed', () => {
- expect(
- withAlphaVariable({
- color: 'currentColor',
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- 'background-color': 'currentColor',
- })
- expect(
- withAlphaVariable({
- color: 'rgb(255, 0)',
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- 'background-color': 'rgb(255, 0)',
- })
- expect(
- withAlphaVariable({
- color: 'rgb(255)',
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- 'background-color': 'rgb(255)',
- })
- expect(
- withAlphaVariable({
- color: 'rgb(255, 0, 0, 255)',
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- 'background-color': 'rgb(255, 0, 0, 255)',
- })
- expect(
- withAlphaVariable({
- color: 'rgb(var(--color))',
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- 'background-color': 'rgb(var(--color))',
- })
- })
-
- test('it ignores colors that already have an alpha channel', () => {
- expect(
- withAlphaVariable({
- color: '#ff0000ff',
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- 'background-color': '#ff0000ff',
- })
- expect(
- withAlphaVariable({
- color: '#ff000080',
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- 'background-color': '#ff000080',
- })
- expect(
- withAlphaVariable({
- color: '#f00a',
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- 'background-color': '#f00a',
- })
- expect(
- withAlphaVariable({
- color: '#f00f',
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- 'background-color': '#f00f',
- })
- expect(
- withAlphaVariable({
- color: 'rgba(255, 255, 255, 1)',
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- 'background-color': 'rgba(255, 255, 255, 1)',
- })
- expect(
- withAlphaVariable({
- color: 'rgba(255, 255, 255, 0.5)',
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- 'background-color': 'rgba(255, 255, 255, 0.5)',
- })
- expect(
- withAlphaVariable({
- color: 'rgba(255 255 255 / 0.5)',
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- 'background-color': 'rgba(255 255 255 / 0.5)',
- })
- expect(
- withAlphaVariable({
- color: 'hsla(240, 100%, 50%, 1)',
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- 'background-color': 'hsla(240, 100%, 50%, 1)',
- })
- expect(
- withAlphaVariable({
- color: 'hsla(240, 100%, 50%, 0.5)',
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- 'background-color': 'hsla(240, 100%, 50%, 0.5)',
- })
- expect(
- withAlphaVariable({
- color: 'hsl(240 100% 50% / 0.5)',
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- 'background-color': 'hsl(240 100% 50% / 0.5)',
- })
- })
-
- test('it allows a closure to be passed', () => {
- expect(
- withAlphaVariable({
- color: ({ opacityVariable }) => `rgba(0, 0, 0, var(${opacityVariable}))`,
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- '--tw-bg-opacity': '1',
- 'background-color': 'rgba(0, 0, 0, var(--tw-bg-opacity))',
- })
- expect(
- withAlphaVariable({
- color: ({ opacityValue }) => `rgba(0, 0, 0, ${opacityValue})`,
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- '--tw-bg-opacity': '1',
- 'background-color': 'rgba(0, 0, 0, var(--tw-bg-opacity))',
- })
- })
-
- test('it transforms rgb and hsl to space-separated rgb and hsl', () => {
- expect(
- withAlphaVariable({
- color: 'rgb(50, 50, 50)',
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- '--tw-bg-opacity': '1',
- 'background-color': 'rgb(50 50 50 / var(--tw-bg-opacity))',
- })
- expect(
- withAlphaVariable({
- color: 'rgb(50 50 50)',
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- '--tw-bg-opacity': '1',
- 'background-color': 'rgb(50 50 50 / var(--tw-bg-opacity))',
- })
- expect(
- withAlphaVariable({
- color: 'hsl(50, 50%, 50%)',
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- '--tw-bg-opacity': '1',
- 'background-color': 'hsl(50 50% 50% / var(--tw-bg-opacity))',
- })
- expect(
- withAlphaVariable({
- color: 'hsl(50 50% 50%)',
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- '--tw-bg-opacity': '1',
- 'background-color': 'hsl(50 50% 50% / var(--tw-bg-opacity))',
- })
- })
-
- test('it transforms named colors to rgb', () => {
- expect(
- withAlphaVariable({
- color: 'red',
- property: 'background-color',
- variable: '--tw-bg-opacity',
- })
- ).toEqual({
- '--tw-bg-opacity': '1',
- 'background-color': 'rgb(255 0 0 / var(--tw-bg-opacity))',
+ expect(
+ withAlphaVariable({
+ color: 'hsl(240 100% 50%)',
+ property: 'color',
+ variable: '--tw-text-opacity',
})
+ ).toEqual({
+ '--tw-text-opacity': '1',
+ color: 'hsl(240 100% 50% / var(--tw-text-opacity))',
+ })
+})
+
+test('it ignores colors that cannot be parsed', () => {
+ expect(
+ withAlphaVariable({
+ color: 'currentColor',
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ 'background-color': 'currentColor',
+ })
+ expect(
+ withAlphaVariable({
+ color: 'rgb(255, 0)',
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ 'background-color': 'rgb(255, 0)',
+ })
+ expect(
+ withAlphaVariable({
+ color: 'rgb(255)',
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ 'background-color': 'rgb(255)',
+ })
+ expect(
+ withAlphaVariable({
+ color: 'rgb(255, 0, 0, 255)',
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ 'background-color': 'rgb(255, 0, 0, 255)',
+ })
+ expect(
+ withAlphaVariable({
+ color: 'rgb(var(--color))',
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ 'background-color': 'rgb(var(--color))',
+ })
+})
+
+test('it ignores colors that already have an alpha channel', () => {
+ expect(
+ withAlphaVariable({
+ color: '#ff0000ff',
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ 'background-color': '#ff0000ff',
+ })
+ expect(
+ withAlphaVariable({
+ color: '#ff000080',
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ 'background-color': '#ff000080',
+ })
+ expect(
+ withAlphaVariable({
+ color: '#f00a',
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ 'background-color': '#f00a',
+ })
+ expect(
+ withAlphaVariable({
+ color: '#f00f',
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ 'background-color': '#f00f',
+ })
+ expect(
+ withAlphaVariable({
+ color: 'rgba(255, 255, 255, 1)',
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ 'background-color': 'rgba(255, 255, 255, 1)',
+ })
+ expect(
+ withAlphaVariable({
+ color: 'rgba(255, 255, 255, 0.5)',
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ 'background-color': 'rgba(255, 255, 255, 0.5)',
+ })
+ expect(
+ withAlphaVariable({
+ color: 'rgba(255 255 255 / 0.5)',
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ 'background-color': 'rgba(255 255 255 / 0.5)',
+ })
+ expect(
+ withAlphaVariable({
+ color: 'hsla(240, 100%, 50%, 1)',
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ 'background-color': 'hsla(240, 100%, 50%, 1)',
+ })
+ expect(
+ withAlphaVariable({
+ color: 'hsla(240, 100%, 50%, 0.5)',
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ 'background-color': 'hsla(240, 100%, 50%, 0.5)',
+ })
+ expect(
+ withAlphaVariable({
+ color: 'hsl(240 100% 50% / 0.5)',
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ 'background-color': 'hsl(240 100% 50% / 0.5)',
+ })
+})
+
+test('it allows a closure to be passed', () => {
+ expect(
+ withAlphaVariable({
+ color: ({ opacityVariable }) => `rgba(0, 0, 0, var(${opacityVariable}))`,
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ '--tw-bg-opacity': '1',
+ 'background-color': 'rgba(0, 0, 0, var(--tw-bg-opacity))',
+ })
+ expect(
+ withAlphaVariable({
+ color: ({ opacityValue }) => `rgba(0, 0, 0, ${opacityValue})`,
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ '--tw-bg-opacity': '1',
+ 'background-color': 'rgba(0, 0, 0, var(--tw-bg-opacity))',
+ })
+})
+
+test('it transforms rgb and hsl to space-separated rgb and hsl', () => {
+ expect(
+ withAlphaVariable({
+ color: 'rgb(50, 50, 50)',
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ '--tw-bg-opacity': '1',
+ 'background-color': 'rgb(50 50 50 / var(--tw-bg-opacity))',
+ })
+ expect(
+ withAlphaVariable({
+ color: 'rgb(50 50 50)',
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ '--tw-bg-opacity': '1',
+ 'background-color': 'rgb(50 50 50 / var(--tw-bg-opacity))',
+ })
+ expect(
+ withAlphaVariable({
+ color: 'hsl(50, 50%, 50%)',
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ '--tw-bg-opacity': '1',
+ 'background-color': 'hsl(50 50% 50% / var(--tw-bg-opacity))',
+ })
+ expect(
+ withAlphaVariable({
+ color: 'hsl(50 50% 50%)',
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ '--tw-bg-opacity': '1',
+ 'background-color': 'hsl(50 50% 50% / var(--tw-bg-opacity))',
+ })
+})
+
+test('it transforms named colors to rgb', () => {
+ expect(
+ withAlphaVariable({
+ color: 'red',
+ property: 'background-color',
+ variable: '--tw-bg-opacity',
+ })
+ ).toEqual({
+ '--tw-bg-opacity': '1',
+ 'background-color': 'rgb(255 0 0 / var(--tw-bg-opacity))',
})
})
diff --git a/turbo.json b/turbo.json
index ef1c9043d..8ddd398f5 100644
--- a/turbo.json
+++ b/turbo.json
@@ -2,7 +2,7 @@
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"//#build": {
- "outputs": ["lib/**", "peers/**", "types/generated/**"],
+ "outputs": ["lib/**", "types/generated/**"],
"inputs": ["src/**", "oxide/**", "nesting/**", "oxide-node-api-shim/**", "types/**"]
},
"test": {
diff --git a/types/config.d.ts b/types/config.d.ts
index bb40c8b71..98ce44a6a 100644
--- a/types/config.d.ts
+++ b/types/config.d.ts
@@ -60,10 +60,11 @@ type FutureConfigValues =
| 'respectDefaultRingColorOpacity'
| 'disableColorOpacityUtilitiesByDefault'
| 'relativeContentPathsByDefault'
+ | 'logicalSiblingUtilities'
type FutureConfig = Expand<'all' | Partial>> | []
// Experimental related config
-type ExperimentalConfigValues = 'optimizeUniversalDefaults' | 'matchVariant'
+type ExperimentalConfigValues = 'optimizeUniversalDefaults' | 'oxideParser'
type ExperimentalConfig = Expand<'all' | Partial>> | []
// DarkMode related config