import fs from 'fs' import path from 'path' import { run, html, css, defaults } 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 rounded-md bg-blue-500 px-4 py-2; } .class-order { @apply p-8 px-3 py-7 pt-4 pr-1; } .with-additional-properties { font-weight: 500; @apply text-right; } .variants { @apply font-semibold hover:font-bold focus:font-medium lg:font-light xl:focus:font-black; } .only-variants { @apply hover:font-bold focus:font-medium lg:font-light xl:focus:font-black; } .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 rounded-md bg-blue-500 px-4 py-2; } .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; } .complex-utilities { @apply ordinal tabular-nums shadow-lg hover:shadow-xl focus:diagonal-fractions; } .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 rounded py-2 px-4 font-bold; } .btn-blue { @apply btn bg-blue-500 text-white hover:bg-blue-700; } .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 sm:text-3xl lg:text-2xl; } h2 { @apply text-2xl; @apply lg:text-2xl; @apply sm:text-2xl; } .important-modifier { @apply !rounded-md px-4; } .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 error when using .peer utility', async () => { let config = { darkMode: 'class', content: [{ raw: '' }], } let input = css` @tailwind components; @tailwind utilities; @layer components { .foo { @apply peer; } } ` await expect(run(input, config)).rejects.toThrowError( `@apply should not be used with the 'peer' utility` ) }) test('@apply error when using a prefixed .peer utility', async () => { let config = { prefix: 'tw-', darkMode: 'class', content: [{ raw: html`` }], } let input = css` @tailwind components; @tailwind utilities; @layer components { .foo { @apply tw-peer; } } ` await expect(run(input, config)).rejects.toThrowError( `@apply should not be used with the 'tw-peer' 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-line: 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-line: underline; text-decoration-line: 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-line: none; } `) }) }) it('should remove duplicate properties when using apply with similar properties', () => { let config = { content: [{ raw: 'foo' }], } let input = css` @tailwind utilities; .foo { @apply absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transform; } ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` .foo { position: absolute; top: 50%; left: 50%; --tw-translate-x: -50%; --tw-translate-y: -50%; 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)); } `) }) }) it('should apply all the definitions of a class', () => { let config = { content: [{ raw: html`` }], plugins: [], } let input = css` @tailwind components; @tailwind utilities; @layer utilities { .aspect-w-1 { position: relative; } .aspect-w-1 { --tw-aspect-w: 1; } } @layer components { .foo { @apply aspect-w-1; } } ` return run(input, config).then((result) => { return expect(result.css).toMatchFormattedCss(css` .foo { position: relative; --tw-aspect-w: 1; } `) }) }) it('should throw when trying to apply a direct circular dependency', () => { let config = { content: [{ raw: html`` }], plugins: [], } let input = css` @tailwind components; @tailwind utilities; @layer components { .foo:not(.text-red-500) { @apply text-red-500; } } ` return run(input, config).catch((err) => { expect(err.reason).toBe( 'You cannot `@apply` the `text-red-500` utility here because it creates a circular dependency.' ) }) }) it('should throw when trying to apply an indirect circular dependency', () => { let config = { content: [{ raw: html`` }], plugins: [], } let input = css` @tailwind components; @tailwind utilities; @layer components { .a { @apply b; } .b { @apply c; } .c { @apply a; } } ` return run(input, config).catch((err) => { expect(err.reason).toBe( 'You cannot `@apply` the `a` utility here because it creates a circular dependency.' ) }) }) it('should not throw when the selector is different (but contains the base partially)', () => { let config = { content: [{ raw: html`` }], plugins: [], } let input = css` @tailwind components; @tailwind utilities; .focus\:bg-gray-500 { @apply bg-gray-500; } ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` .bg-gray-500 { --tw-bg-opacity: 1; background-color: rgb(107 114 128 / var(--tw-bg-opacity)); } .focus\:bg-gray-500 { --tw-bg-opacity: 1; background-color: rgb(107 114 128 / var(--tw-bg-opacity)); } `) }) }) it('should throw when trying to apply an indirect circular dependency with a modifier (1)', () => { let config = { content: [{ raw: html`` }], plugins: [], } let input = css` @tailwind components; @tailwind utilities; @layer components { .a { @apply b; } .b { @apply c; } .c { @apply hover:a; } } ` return run(input, config).catch((err) => { expect(err.reason).toBe( 'You cannot `@apply` the `hover:a` utility here because it creates a circular dependency.' ) }) }) it('should throw when trying to apply an indirect circular dependency with a modifier (2)', () => { let config = { content: [{ raw: html`` }], plugins: [], } let input = css` @tailwind components; @tailwind utilities; @layer components { .a { @apply b; } .b { @apply hover:c; } .c { @apply a; } } ` return run(input, config).catch((err) => { expect(err.reason).toBe( 'You cannot `@apply` the `a` utility here because it creates a circular dependency.' ) }) }) it('should not throw when the circular dependency is part of a different selector (1)', () => { let config = { content: [{ raw: html`` }], plugins: [], } let input = css` @tailwind utilities; @layer utilities { html.dark .a, .b { color: red; } } html.dark .c { @apply b; } ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` html.dark .c { color: red; } `) }) }) it('should not throw when the circular dependency is part of a different selector (2)', () => { let config = { content: [{ raw: html`` }], plugins: [], } let input = css` @tailwind utilities; @layer utilities { html.dark .a, .b { color: red; } } html.dark .c { @apply hover:b; } ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` html.dark .c:hover { color: red; } `) }) }) it('should throw when the circular dependency is part of the same selector', () => { let config = { content: [{ raw: html`` }], plugins: [], } let input = css` @tailwind utilities; @layer utilities { html.dark .a, html.dark .b { color: red; } } html.dark .c { @apply hover:b; } ` return run(input, config).catch((err) => { expect(err.reason).toBe( 'You cannot `@apply` the `hover:b` utility here because it creates a circular dependency.' ) }) }) it('rules with vendor prefixes are still separate when optimizing defaults rules', () => { let config = { experimental: { optimizeUniversalDefaults: true }, content: [{ raw: html`` }], corePlugins: { preflight: false }, } let input = css` @tailwind base; @tailwind components; @tailwind utilities; @layer components { input[type='range']::-moz-range-thumb { @apply border; } } ` return run(input, config).then((result) => { return expect(result.css).toMatchFormattedCss(css` input[type='range']::-moz-range-thumb { border-width: 1px; } .border { border-width: 1px; } `) }) }) it('should be possible to apply user css', () => { let config = { content: [{ raw: html`` }], plugins: [], } let input = css` @tailwind components; @tailwind utilities; .foo { color: red; } .bar { @apply foo; } ` return run(input, config).then((result) => { return expect(result.css).toMatchFormattedCss(css` .foo { color: red; } .bar { color: red; } `) }) }) it('should not be possible to apply user css with variants', () => { let config = { content: [{ raw: html`` }], plugins: [], } let input = css` @tailwind components; @tailwind utilities; .foo { color: red; } .bar { @apply hover:foo; } ` return run(input, config).catch((err) => { expect(err.reason).toBe( 'The `hover:foo` class does not exist. If `hover:foo` is a custom class, make sure it is defined within a `@layer` directive.' ) }) }) it('should not apply unrelated siblings when applying something from within atrules', () => { let config = { content: [{ raw: html`` }], plugins: [], } let input = css` @tailwind components; @tailwind utilities; @layer components { .foo { font-weight: bold; @apply bar; } .bar { color: green; } @supports (a: b) { .bar { color: blue; } .something-unrelated { color: red; } } } ` return run(input, config).then((result) => { expect(result.css).toMatchFormattedCss(css` .foo { font-weight: bold; color: green; } @supports (a: b) { .foo { color: blue; } } .bar { color: green; } @supports (a: b) { .bar { color: blue; } .something-unrelated { color: red; } } `) }) }) it('should be possible to apply user css without tailwind directives', () => { let config = { content: [{ raw: html`` }], plugins: [], } let input = css` .bop { color: red; } .bar { background-color: blue; } .foo { @apply bar bop absolute; } ` return run(input, config).then((result) => { return expect(result.css).toMatchFormattedCss(css` .bop { color: red; } .bar { background-color: blue; } .foo { position: absolute; color: red; background-color: blue; } `) }) }) it('should be possible to apply a class from another rule with multiple selectors (2 classes)', () => { let config = { content: [{ raw: html`` }], plugins: [], } let input = css` @tailwind utilities; @layer utilities { .a, .b { @apply underline; } .c { @apply b; } } ` return run(input, config).then((result) => { return expect(result.css).toMatchFormattedCss(css` .c { text-decoration-line: underline; } `) }) }) it('should be possible to apply a class from another rule with multiple selectors (1 class, 1 tag)', () => { let config = { content: [{ raw: html`` }], plugins: [], } let input = css` @tailwind utilities; @layer utilities { span, .b { @apply underline; } .c { @apply b; } } ` return run(input, config).then((result) => { return expect(result.css).toMatchFormattedCss(css` span, .b { text-decoration-line: underline; } .c { text-decoration-line: underline; } `) }) }) it('should be possible to apply a class from another rule with multiple selectors (1 class, 1 id)', () => { let config = { content: [{ raw: html`` }], plugins: [], } let input = css` @tailwind utilities; @layer utilities { #a, .b { @apply underline; } .c { @apply b; } } ` return run(input, config).then((result) => { return expect(result.css).toMatchFormattedCss(css` #a, .b { text-decoration-line: underline; } .c { text-decoration-line: underline; } `) }) }) describe('multiple instances', () => { it('should be possible to apply multiple "instances" of the same class', () => { let config = { content: [{ raw: html`` }], plugins: [], corePlugins: { preflight: false }, } let input = css` .a { @apply b; } .b { @apply uppercase; } .b { color: red; } ` return run(input, config).then((result) => { return expect(result.css).toMatchFormattedCss(css` .a { text-transform: uppercase; color: red; } .b { text-transform: uppercase; color: red; } `) }) }) it('should be possible to apply a combination of multiple "instances" of the same class', () => { let config = { content: [{ raw: html`` }], plugins: [], corePlugins: { preflight: false }, } let input = css` .a { @apply b; } .b { @apply uppercase; color: red; } ` return run(input, config).then((result) => { return expect(result.css).toMatchFormattedCss(css` .a { text-transform: uppercase; color: red; } .b { text-transform: uppercase; color: red; } `) }) }) it('should generate the same output, even if it was used in a @layer', () => { let config = { content: [{ raw: html`` }], plugins: [], corePlugins: { preflight: false }, } let input = css` @tailwind components; @layer components { .a { @apply b; } .b { @apply uppercase; color: red; } } ` return run(input, config).then((result) => { return expect(result.css).toMatchFormattedCss(css` .a { text-transform: uppercase; color: red; } .b { text-transform: uppercase; color: red; } `) }) }) it('should be possible to apply a combination of multiple "instances" of the same class (defined in a layer)', () => { let config = { content: [{ raw: html`` }], plugins: [], corePlugins: { preflight: false }, } let input = css` @tailwind components; @layer components { .a { color: red; @apply b; color: blue; } .b { @apply text-green-500; text-decoration: underline; } } ` return run(input, config).then((result) => { return expect(result.css).toMatchFormattedCss(css` .a { color: red; --tw-text-opacity: 1; color: rgb(34 197 94 / var(--tw-text-opacity)); text-decoration: underline; color: blue; } .b { --tw-text-opacity: 1; color: rgb(34 197 94 / var(--tw-text-opacity)); text-decoration: underline; } `) }) }) it('should properly maintain the order', () => { let config = { content: [{ raw: html`` }], plugins: [], corePlugins: { preflight: false }, } let input = css` h2 { @apply text-xl; @apply lg:text-3xl; @apply sm:text-2xl; } ` return run(input, config).then((result) => { return expect(result.css).toMatchFormattedCss(css` h2 { font-size: 1.25rem; line-height: 1.75rem; } @media (min-width: 1024px) { h2 { font-size: 1.875rem; line-height: 2.25rem; } } @media (min-width: 640px) { h2 { font-size: 1.5rem; line-height: 2rem; } } `) }) }) }) it('apply can emit defaults in isolated environments without @tailwind directives', () => { let config = { experimental: { optimizeUniversalDefaults: true }, content: [{ raw: html`` }], } let input = css` .foo { @apply focus:rotate-90; } ` return run(input, config).then((result) => { return expect(result.css).toMatchFormattedCss(css` .foo:focus { --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)); } `) }) }) it('apply does not emit defaults in isolated environments without optimizeUniversalDefaults', () => { let config = { experimental: { optimizeUniversalDefaults: false }, content: [{ raw: html`` }], corePlugins: { preflight: false }, } let input = css` @tailwind base; .foo { @apply focus:rotate-90; } ` return run(input, config).then((result) => { return expect(result.css).toMatchFormattedCss(css` ${defaults} .foo:focus { --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)); } `) }) }) it('should work outside of layer', async () => { let config = { content: [{ raw: html`` }], corePlugins: { preflight: false }, } let input = css` .input-text { @apply bg-white; background-color: red; } ` let result result = await run(input, config) expect(result.css).toMatchFormattedCss(css` .input-text { --tw-bg-opacity: 1; background-color: rgb(255 255 255 / var(--tw-bg-opacity)); background-color: red; } `) result = await run(input, config) expect(result.css).toMatchFormattedCss(css` .input-text { --tw-bg-opacity: 1; background-color: rgb(255 255 255 / var(--tw-bg-opacity)); background-color: red; } `) }) it('should work in layer', async () => { let config = { content: [{ raw: html`` }], corePlugins: { preflight: false }, } let input = css` @tailwind components; @layer components { .input-text { @apply bg-white; background-color: red; } } ` await run(input, config) const result = await run(input, config) expect(result.css).toMatchFormattedCss(css` .input-text { --tw-bg-opacity: 1; background-color: rgb(255 255 255 / var(--tw-bg-opacity)); background-color: red; } `) }) it('apply partitioning works with media queries', async () => { let config = { content: [{ raw: html`` }], corePlugins: { preflight: false }, } let input = css` @tailwind base; @layer base { html, body { @apply text-green-600; font-size: 1rem; } @media print { html, body { @apply text-red-600; font-size: 2rem; } } } ` await run(input, config) const result = await run(input, config) expect(result.css).toMatchFormattedCss(css` html, body { --tw-text-opacity: 1; color: rgb(22 163 74 / var(--tw-text-opacity)); font-size: 1rem; } @media print { html, body { --tw-text-opacity: 1; color: rgb(220 38 38 / var(--tw-text-opacity)); font-size: 2rem; } } ${defaults} `) }) it('should be possible to use apply in plugins', async () => { let config = { content: [{ raw: html`` }], corePlugins: { preflight: false }, plugins: [ function ({ addComponents }) { addComponents({ '.a': { color: 'red', }, '.b': { '@apply a': {}, color: 'blue', }, }) }, ], } return run('@tailwind components', config).then((result) => { expect(result.css).toMatchFormattedCss(css` .a { color: red; } .b { color: red; color: blue; } `) }) }) it('should apply using the updated user CSS when the source has changed', async () => { let config = { content: [{ raw: html`` }], plugins: [], } let inputBefore = css` .foo { color: green; } .bar { @apply foo; } ` let inputAfter = css` .foo { color: red; } .bar { @apply foo; } ` let result = await run(inputBefore, config) expect(result.css).toMatchFormattedCss(css` .foo { color: green; } .bar { color: green; } `) result = await run(inputAfter, config) expect(result.css).toMatchFormattedCss(css` .foo { color: red; } .bar { color: red; } `) }) it('apply + layer utilities + selector variants (like group) + important selector', async () => { let config = { important: '#myselector', content: [{ raw: html`` }], plugins: [], } let input = css` @tailwind utilities; @layer utilities { .custom-utility { @apply font-normal group-hover:underline; } } ` let result = await run(input, config) expect(result.css).toMatchFormattedCss(css` #myselector .custom-utility { font-weight: 400; } #myselector .group:hover .custom-utility { text-decoration-line: underline; } `) }) it('apply + user CSS + selector variants (like group) + important selector (1)', async () => { let config = { important: '#myselector', content: [{ raw: html`` }], plugins: [], } let input = css` .custom-utility { @apply font-normal group-hover:underline; } ` let result = await run(input, config) expect(result.css).toMatchFormattedCss(css` .custom-utility { font-weight: 400; } .group:hover .custom-utility { text-decoration-line: underline; } `) }) it('apply + user CSS + selector variants (like group) + important selector (2)', async () => { let config = { important: '#myselector', content: [{ raw: html`` }], plugins: [], } let input = css` #myselector .custom-utility { @apply font-normal group-hover:underline; } ` let result = await run(input, config) expect(result.css).toMatchFormattedCss(css` #myselector .custom-utility { font-weight: 400; } .group:hover #myselector .custom-utility { text-decoration-line: underline; } `) }) it('can apply user utilities that start with a dash', async () => { let config = { content: [{ raw: html`` }], plugins: [], } let input = css` @tailwind utilities; @layer utilities { .foo-1 { margin: 10px; } .-foo-1 { margin: -15px; } .new-class { @apply -foo-1; } } ` let result = await run(input, config) expect(result.css).toMatchFormattedCss(css` .foo-1 { margin: 10px; } .-foo-1 { margin: -15px; } .new-class { margin: -15px; } `) }) it('can apply joined classes when using elements', async () => { let config = { content: [{ raw: html`` }], plugins: [], } let input = css` .foo.bar { color: red; } .bar.foo { color: green; } header:nth-of-type(odd) { @apply foo; } main { @apply foo bar; } footer { @apply bar; } ` let result = await run(input, config) expect(result.css).toMatchFormattedCss(css` .foo.bar { color: red; } .bar.foo { color: green; } header.bar:nth-of-type(odd) { color: red; color: green; } main.bar { color: red; } main.foo { color: red; } main.bar { color: green; } main.foo { color: green; } footer.foo { color: red; color: green; } `) }) it('should not replace multiple instances of the same class in a single selector', async () => { // NOTE: This test is non-normative and is not part of the spec of how `@apply` works per-se // It describes how it currently works because the "correct" way produces a combinatorial explosion // of selectors that is not easily doable let config = { content: [{ raw: html`` }], plugins: [], } let input = css` .foo + .foo { color: blue; } .bar + .bar { color: fuchsia; } header { @apply foo; } main { @apply foo bar; } footer { @apply bar; } ` let result = await run(input, config) expect(result.css).toMatchFormattedCss(css` .foo + .foo { color: blue; } .bar + .bar { color: fuchsia; } header + .foo { color: blue; } main + .foo { color: blue; } main + .bar { color: fuchsia; } footer + .bar { color: fuchsia; } `) })