30 lines
1.6 KiB
TypeScript
30 lines
1.6 KiB
TypeScript
import { readFileSync, readdirSync } from 'node:fs'
|
|
import { join } from 'node:path'
|
|
import { expect, it } from 'vitest'
|
|
|
|
const dir = join(process.cwd(), 'src/client')
|
|
const css = readFileSync(join(dir, 'client.css'), 'utf8')
|
|
const code = readdirSync(dir).filter(name => /\.tsx?$/.test(name)).map(name => readFileSync(join(dir, name), 'utf8')).join('\n')
|
|
|
|
it('styles only classes a component uses', () => {
|
|
// Template literals like `dt-btn-${variant}` match any class with that prefix.
|
|
const prefixes = [...code.matchAll(/(dt-[\w-]*-)\$\{/g)].map(match => match[1]!)
|
|
const used = (name: string) => new RegExp(`(?<![\\w-])${name}(?![\\w-])`).test(code) || prefixes.some(prefix => name.startsWith(prefix))
|
|
const classes = new Set([...css.replace(/\/\*[\s\S]*?\*\//g, '').matchAll(/\.(dt-[\w-]+)/g)].map(match => match[1]!))
|
|
expect([...classes].filter(name => !used(name))).toEqual([])
|
|
})
|
|
|
|
it('writes one rule per line, each selector once within its block', () => {
|
|
const blocks: string[][] = [[]]
|
|
for (const line of css.replace(/\/\*[\s\S]*?\*\//g, '').split('\n').map(text => text.trim()).filter(Boolean)) {
|
|
if (line === '}') { blocks.pop(); continue }
|
|
if (/^@(media|container|supports)\b.*\{$/.test(line)) { blocks.push([]); continue }
|
|
// A rule, or a keyframes rule with its steps, opens and closes on its line.
|
|
expect(line).toMatch(/^[^{}]+\{[^{}]*\}$|^@keyframes [\w-]+ \{( [^{}]+ \{[^{}]*\})+ \}$/)
|
|
if (line.startsWith('@')) continue
|
|
const selector = line.slice(0, line.indexOf('{')).trim()
|
|
expect(blocks.at(-1)).not.toContain(selector)
|
|
blocks.at(-1)!.push(selector)
|
|
}
|
|
})
|