`
const includes = [
`font-['some_font',sans-serif]`,
`font-["some_font",sans-serif]`,
`bg-[url('...')]`,
`bg-[url("...")]`,
`bg-[url('...'),url('...')]`,
`bg-[url("..."),url("...")]`,
`content-['hello']`,
`content-["hello"]`,
`[content:'hello']`,
`[content:"hello"]`,
`[content:"hello"]`,
`[content:'hello']`,
`fill-[#bada55]`,
`fill-[#bada55]/50`,
`px-1.5`,
`pl-1.5`,
`pl-2.5`,
`pr-1.5`,
`pr-2.5`,
`pr-3.5`,
`pr-4.5`,
`pr-5.5`,
`pr-6.5`,
`ml-0.5`,
`h-[100px]`,
`h-[101px]`,
`h-[102px]`,
`h-[103px]`,
`h-[104px]`,
`h-[105px]`,
`h-[106px]`,
`h-[107px]`,
`h-[108px]`,
`h-[109px]`,
`uppercase`,
`lowercase`,
`normal-case`,
`hover:font-bold`,
`text-sm`,
`text-[10px]`,
`text-[11px]`,
`text-blue-500`,
`text-[21px]`,
`text-[22px]`,
`text-[31px]`,
`text-[32px]`,
`text-[41px]`,
`text-[42px]`,
`text-[51px]`,
`text-[52px]`,
`text-[61px]`,
`text-[62px]`,
`text-[71px]`,
`text-[72px]`,
`lg:text-[4px]`,
`lg:text-[24px]`,
`content-['>']`,
`hover:test`,
`overflow-scroll`,
`[--y:theme(colors.blue.500)]`,
`w-[calc(100%-theme('spacing.1'))]`,
`w-[calc(100%-theme("spacing.2"))]`,
]
const excludes = [
`uppercase:`,
'hover:',
"hover:'abc",
`font-bold`,
`
`,
`test`,
]
test('The default extractor works as expected', async () => {
const extractions = defaultExtractor([jsExamples, jsxExamples, htmlExamples].join('\n').trim())
for (const str of includes) {
expect(extractions).toContain(str)
}
for (const str of excludes) {
expect(extractions).not.toContain(str)
}
})
// Scenarios:
// - In double quoted class attribute
// - In single quoted class attribute
// - Single-quoted as a variable
// - Double-quoted as a variable
// - Single-quoted as first array item
// - Double-quoted as first array item
// - Single-quoted as middle array item
// - Double-quoted as middle array item
// - Single-quoted as last array item
// - Double-quoted as last array item
// - Bare as an object key (with trailing `:`)
// - Quoted as an object key (with trailing `:`)
// - Within a template literal
// - Within a template literal directly before interpolation
// - Within a template literal directly after interpolation
// - JS: ${...}
// - PHP: {$...}
// - Ruby: #{...}
// - Within a string of HTML wrapped in escaped quotes
test('basic utility classes', async () => {
const extractions = defaultExtractor(`
`)
expect(extractions).toContain('text-center')
expect(extractions).toContain('font-bold')
expect(extractions).toContain('px-4')
expect(extractions).toContain('pointer-events-none')
})
test('modifiers with basic utilites', async () => {
const extractions = defaultExtractor(`
`)
expect(extractions).toContain('hover:text-center')
expect(extractions).toContain('hover:focus:font-bold')
})
test('utilities with dot characters', async () => {
const extractions = defaultExtractor(`
`)
expect(extractions).toContain('px-1.5')
expect(extractions).toContain('active:px-2.5')
expect(extractions).toContain('hover:focus:px-3.5')
})
test('basic utilities with color opacity modifier', async () => {
const extractions = defaultExtractor(`
`)
expect(extractions).toContain('text-red-500/25')
expect(extractions).toContain('hover:text-red-500/50')
expect(extractions).toContain('hover:active:text-red-500/75')
})
test('basic arbitrary values', async () => {
const extractions = defaultExtractor(`
`)
expect(extractions).toContain('px-[25px]')
expect(extractions).toContain('hover:px-[40rem]')
expect(extractions).toContain('hover:focus:px-[23vh]')
})
test('arbitrary values with color opacity modifier', async () => {
const extractions = defaultExtractor(`
`)
expect(extractions).toContain('text-[#bada55]/25')
expect(extractions).toContain('hover:text-[#bada55]/50')
expect(extractions).toContain('hover:active:text-[#bada55]/75')
})
test('arbitrary values with spaces', async () => {
const extractions = defaultExtractor(`
`)
expect(extractions).toContain('grid-cols-[1fr_200px_3fr]')
expect(extractions).toContain('md:grid-cols-[2fr_100px_1fr]')
expect(extractions).toContain('open:lg:grid-cols-[3fr_300px_1fr]')
})
test('arbitrary values with css variables', async () => {
const extractions = defaultExtractor(`
`)
expect(extractions).toContain('fill-[var(--my-color)]')
expect(extractions).toContain('hover:fill-[var(--my-color-2)]')
expect(extractions).toContain('hover:focus:fill-[var(--my-color-3)]')
})
test('arbitrary values with type hints', async () => {
const extractions = defaultExtractor(`
`)
expect(extractions).toContain('text-[color:var(--my-color)]')
expect(extractions).toContain('hover:text-[color:var(--my-color-2)]')
expect(extractions).toContain('hover:focus:text-[color:var(--my-color-3)]')
})
test('arbitrary values with single quotes', async () => {
const extractions = defaultExtractor(`
`)
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 () => {
const extractions = defaultExtractor(`
`)
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 () => {
const extractions = defaultExtractor(`
`)
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 () => {
const extractions = defaultExtractor(`
`)
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 () => {
const extractions = defaultExtractor(`
`)
expect(extractions).toContain(`content-['hello\\_world']`)
expect(extractions).toContain(`hover:content-['hello\\_world\\_2']`)
expect(extractions).toContain(`hover:focus:content-['hello\\_world\\_3']`)
})
test('basic utilities with arbitrary color opacity modifier', async () => {
const extractions = defaultExtractor(`
`)
expect(extractions).toContain('text-red-500/[.25]')
expect(extractions).toContain('hover:text-red-500/[.5]')
expect(extractions).toContain('hover:active:text-red-500/[.75]')
})
test('arbitrary values with arbitrary color opacity modifier', async () => {
const extractions = defaultExtractor(`
`)
expect(extractions).toContain('text-[#bada55]/[.25]')
expect(extractions).toContain('hover:text-[#bada55]/[.5]')
expect(extractions).toContain('hover:active:text-[#bada55]/[.75]')
})
test('arbitrary values with angle brackets', async () => {
const extractions = defaultExtractor(`
`)
expect(extractions).toContain(`content-[>]`)
expect(extractions).toContain(`hover:content-[<]`)
expect(extractions).toContain(`hover:focus:content-[>]`)
})
test('arbitrary values with angle brackets in single quotes', async () => {
const extractions = defaultExtractor(`
`)
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 () => {
const extractions = defaultExtractor(`
"] 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', () => {
const extractions = defaultExtractor(`
`)
expect(extractions).toContain(`[--y:theme('colors.blue.500')]`)
expect(extractions).toContain(`[color:var(--y)]`)
})
test('special characters', async () => {
const extractions = defaultExtractor(`
`)
expect(extractions).toContain(`
:font-bold`)
})
test('with single quotes array within template literal', async () => {
const extractions = defaultExtractor(``)
expect(extractions).toContain('pr-1.5')
expect(extractions).toContain('pr-1')
})
test('with double quotes array within template literal', async () => {
const extractions = defaultExtractor(``)
expect(extractions).toContain('pr-1.5')
expect(extractions).toContain('pr-1')
})
test('with single quotes array within function', async () => {
const extractions = defaultExtractor(`document.body.classList.add(['pl-1.5'].join(" "));`)
expect(extractions).toContain('pl-1.5')
expect(extractions).toContain('pl-1')
})
test('with double quotes array within function', async () => {
const extractions = defaultExtractor(`document.body.classList.add(["pl-1.5"].join(" "));`)
expect(extractions).toContain('pl-1.5')
expect(extractions).toContain('pl-1')
})