import fs from 'fs' import path from 'path' import { run, html, css } from './util/run' test('@apply', () => { let config = { darkMode: 'class', content: [path.resolve(__dirname, './apply.test.html')], } let input = css` @tailwind components; @tailwind utilities; @layer components { .basic-example { @apply px-4 py-2 bg-blue-500 rounded-md; } .class-order { @apply pt-4 pr-1 px-3 py-7 p-8; } .with-additional-properties { font-weight: 500; @apply text-right; } .variants { @apply xl:focus:font-black hover:font-bold lg:font-light focus:font-medium font-semibold; } .only-variants { @apply xl:focus:font-black hover:font-bold lg:font-light focus:font-medium; } .apply-group-variant { @apply group-hover:text-center lg:group-hover:text-left; } .apply-dark-variant { @apply dark:text-center dark:hover:text-right lg:dark:text-left; } .apply-custom-utility { @apply custom-util hover:custom-util lg:custom-util xl:focus:custom-util; } .multiple, .selectors { @apply px-4 py-2 bg-blue-500 rounded-md; } .multiple-variants, .selectors-variants { @apply hover:text-center active:text-right lg:focus:text-left; } .multiple-group, .selectors-group { @apply group-hover:text-center lg:group-hover:text-left; } /* TODO: This works but the generated CSS is unnecessarily verbose. */ .complex-utilities { @apply ordinal tabular-nums focus:diagonal-fractions shadow-lg hover:shadow-xl; } .basic-nesting-parent { .basic-nesting-child { @apply font-bold hover:font-normal; } } .use-base-only-a { @apply font-bold; } .use-base-only-b { @apply use-base-only-a font-normal; } .use-dependant-only-a { @apply font-bold; } .use-dependant-only-b { @apply use-dependant-only-a font-normal; } .btn { @apply font-bold py-2 px-4 rounded; } .btn-blue { @apply btn bg-blue-500 hover:bg-blue-700 text-white; } .recursive-apply-a { @apply font-black sm:font-thin; } .recursive-apply-b { @apply recursive-apply-a font-semibold md:font-extralight; } .recursive-apply-c { @apply recursive-apply-b font-bold lg:font-light; } .use-with-other-properties-base { color: green; @apply font-bold; } .use-with-other-properties-component { @apply use-with-other-properties-base; } .add-sibling-properties { padding: 2rem; @apply px-4 hover:px-2 lg:px-10 xl:focus:px-1; padding-top: 3px; @apply use-with-other-properties-base; } h1 { @apply text-2xl lg:text-2xl sm:text-3xl; } h2 { @apply text-2xl; @apply lg:text-2xl; @apply sm:text-2xl; } .important-modifier { @apply px-4 !rounded-md; } .important-modifier-variant { @apply px-4 hover:!rounded-md; } } @layer utilities { .custom-util { custom: stuff; } .foo { @apply animate-spin; } .bar { @apply animate-pulse !important; } } ` return run(input, config).then((result) => { let expectedPath = path.resolve(__dirname, './apply.test.css') let expected = fs.readFileSync(expectedPath, 'utf8') expect(result.css).toMatchFormattedCss(expected) }) }) test('@apply error with unknown utility', async () => { let config = { darkMode: 'class', content: [path.resolve(__dirname, './apply.test.html')], } let input = css` @tailwind components; @tailwind utilities; @layer components { .foo { @apply a-utility-that-does-not-exist; } } ` await expect(run(input, config)).rejects.toThrowError('class does not exist') }) test('@apply error with nested @screen', async () => { let config = { darkMode: 'class', content: [path.resolve(__dirname, './apply.test.html')], } let input = css` @tailwind components; @tailwind utilities; @layer components { .foo { @screen md { @apply text-black; } } } ` await expect(run(input, config)).rejects.toThrowError( '@apply is not supported within nested at-rules like @screen' ) }) test('@apply error with nested @anyatrulehere', async () => { let config = { darkMode: 'class', content: [path.resolve(__dirname, './apply.test.html')], } let input = css` @tailwind components; @tailwind utilities; @layer components { .foo { @genie { @apply text-black; } } } ` await expect(run(input, config)).rejects.toThrowError( '@apply is not supported within nested at-rules like @genie' ) }) test('@apply error when using .group utility', async () => { let config = { darkMode: 'class', content: [{ raw: '
' }], } let input = css` @tailwind components; @tailwind utilities; @layer components { .foo { @apply group; } } ` await expect(run(input, config)).rejects.toThrowError( `@apply should not be used with the 'group' utility` ) }) test('@apply error when using a prefixed .group utility', async () => { let config = { prefix: 'tw-', darkMode: 'class', content: [{ raw: html`` }], } let input = css` @tailwind components; @tailwind utilities; @layer components { .foo { @apply tw-group; } } ` await expect(run(input, config)).rejects.toThrowError( `@apply should not be used with the 'tw-group' utility` ) }) test('@apply classes from outside a @layer', async () => { let config = { content: [{ raw: html`` }], } let input = css` @tailwind components; @tailwind utilities; .foo { @apply font-bold; } .bar { @apply foo text-red-500 hover:text-green-500; } .baz { @apply bar underline; } .keep-me-even-though-I-am-not-used-in-content { color: green; } ` await run(input, config).then((result) => { return expect(result.css).toMatchFormattedCss(css` .font-bold { font-weight: 700; } .foo { font-weight: 700; } .bar { --tw-text-opacity: 1; color: rgb(239 68 68 / var(--tw-text-opacity)); font-weight: 700; } .bar:hover { --tw-text-opacity: 1; color: rgb(34 197 94 / var(--tw-text-opacity)); } .baz { text-decoration: underline; --tw-text-opacity: 1; color: rgb(239 68 68 / var(--tw-text-opacity)); font-weight: 700; } .baz:hover { --tw-text-opacity: 1; color: rgb(34 197 94 / var(--tw-text-opacity)); } .keep-me-even-though-I-am-not-used-in-content { color: green; } `) }) }) test('@applying classes from outside a @layer respects the source order', async () => { let config = { content: [{ raw: html`` }], } let input = css` .baz { @apply bar underline; } @tailwind components; .keep-me-even-though-I-am-not-used-in-content { color: green; } @tailwind utilities; .foo { @apply font-bold; } .bar { @apply no-underline; } ` await run(input, config).then((result) => { return expect(result.css).toMatchFormattedCss(css` .baz { text-decoration: underline; text-decoration: none; } .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; } } .keep-me-even-though-I-am-not-used-in-content { color: green; } .font-bold { font-weight: 700; } .foo { font-weight: 700; } .bar { text-decoration: none; } `) }) })