import { generate, type Rule } from 'css-tree'; import { setupTailwind } from '../tailwindcss/setup-tailwind.js'; import { extractRulesPerClass } from './extract-rules-per-class.js'; describe('extractRulesPerClass()', async () => { function convertToComparable(map: Map): Record { return Object.fromEntries(map.entries().map(([k, v]) => [k, generate(v)])); } it('works with just inlinable utilities', async () => { const tailwind = await setupTailwind({}); const classes = ['text-center', 'bg-red-500']; tailwind.addUtilities(classes); const stylesheet = tailwind.getStyleSheet(); const { inlinable, nonInlinable } = extractRulesPerClass( stylesheet, classes, ); expect(convertToComparable(inlinable)).toMatchInlineSnapshot(` { "bg-red-500": ".bg-red-500{background-color:var(--color-red-500)}", "text-center": ".text-center{text-align:center}", } `); expect(convertToComparable(nonInlinable)).toMatchInlineSnapshot('{}'); }); it('handles non-inlinable utilities', async () => { const tailwind = await setupTailwind({}); const classes = ['lg:w-1/2']; tailwind.addUtilities(classes); const stylesheet = tailwind.getStyleSheet(); const { inlinable, nonInlinable } = extractRulesPerClass( stylesheet, classes, ); expect(convertToComparable(inlinable)).toMatchInlineSnapshot('{}'); expect(convertToComparable(nonInlinable)).toMatchInlineSnapshot(` { "lg:w-1/2": ".lg\\:w-1\\/2{@media (width>=64rem){width:calc(1/2*100%)}}", } `); }); it('handles a mix of inlinable and non-inlinable utilities', async () => { const tailwind = await setupTailwind({}); const classes = [ 'text-center', 'bg-red-500', 'some-other-class', // should be ignored 'w-full', 'lg:w-1/2', ]; tailwind.addUtilities(classes); const stylesheet = tailwind.getStyleSheet(); const { inlinable, nonInlinable } = extractRulesPerClass( stylesheet, classes, ); expect(convertToComparable(inlinable)).toMatchInlineSnapshot(` { "bg-red-500": ".bg-red-500{background-color:var(--color-red-500)}", "text-center": ".text-center{text-align:center}", "w-full": ".w-full{width:100%}", } `); expect(convertToComparable(nonInlinable)).toMatchInlineSnapshot(` { "lg:w-1/2": ".lg\\:w-1\\/2{@media (width>=64rem){width:calc(1/2*100%)}}", } `); }); it('splits mixed rules (base + media) into inlinable base and non-inlinable media', async () => { const tailwind = await setupTailwind({ config: { plugins: [ { handler: (api) => { api.addUtilities({ '.text-body': { '@apply text-[green] sm:text-[darkgreen]': {}, }, }); }, }, ], }, }); const classes = ['text-body']; tailwind.addUtilities(classes); const stylesheet = tailwind.getStyleSheet(); const { inlinable, nonInlinable } = extractRulesPerClass( stylesheet, classes, ); expect(convertToComparable(inlinable)).toMatchInlineSnapshot(` { "text-body": ".text-body{color:green}", } `); expect(convertToComparable(nonInlinable)).toMatchInlineSnapshot(` { "text-body": ".text-body{@media (width>=40rem){color:darkgreen}}", } `); }); it('treats rules with pseudo-selectors as fully non-inlinable', async () => { const tailwind = await setupTailwind({ config: { plugins: [ { handler: (api) => { api.addUtilities({ '.btn:hover': { color: 'red', }, }); }, }, ], }, }); const classes = ['btn']; tailwind.addUtilities(classes); const stylesheet = tailwind.getStyleSheet(); const { inlinable, nonInlinable } = extractRulesPerClass( stylesheet, classes, ); expect(convertToComparable(inlinable)).toMatchInlineSnapshot('{}'); expect(convertToComparable(nonInlinable)).toMatchInlineSnapshot(` { "btn": ".btn{&:hover{color:red}}", } `); }); });