Files
dsh-superagent/tests/styles.spec.ts
T
2026-10-31 20:05:39 +08:00

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)
}
})