96 lines
3.0 KiB
JavaScript
96 lines
3.0 KiB
JavaScript
import { execFileSync } from 'node:child_process'
|
|
import { mkdtempSync, writeFileSync, mkdirSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
const GUARD = join(process.cwd(), 'scripts', 'check-tokens.mjs')
|
|
|
|
function runIn(files) {
|
|
const dir = mkdtempSync(join(tmpdir(), 'token-guard-'))
|
|
mkdirSync(join(dir, 'src'))
|
|
for (const [name, content] of Object.entries(files)) {
|
|
writeFileSync(join(dir, 'src', name), content)
|
|
}
|
|
try {
|
|
execFileSync(process.execPath, [GUARD, join(dir, 'src')], { encoding: 'utf8' })
|
|
return { code: 0, output: '' }
|
|
} catch (err) {
|
|
return { code: err.status, output: err.stdout + String(err.stderr ?? '') }
|
|
}
|
|
}
|
|
|
|
const TOKENS = ':root { --primary: lab(50% 0 0); --space-2: 0.5rem; }\n'
|
|
|
|
describe('check-tokens guard', () => {
|
|
it('passes a clean file', () => {
|
|
const result = runIn({
|
|
'tokens.css': TOKENS,
|
|
'a.tsx': `export const A = () => <div style={{ color: 'var(--primary)', padding: 'var(--space-2)' }} />\n`,
|
|
})
|
|
expect(result.code).toBe(0)
|
|
})
|
|
|
|
it('flags hex, named colors and hsl()/oklch() inside style={{ }}', () => {
|
|
const result = runIn({
|
|
'tokens.css': TOKENS,
|
|
'a.tsx': `export const A = () => (
|
|
<div style={{
|
|
color: '#fff',
|
|
background: 'oklch(70% 0.1 200)',
|
|
borderColor: 'red',
|
|
}} />
|
|
)\n`,
|
|
})
|
|
expect(result.code).toBe(1)
|
|
expect(result.output).toMatch(/hex color #fff/)
|
|
expect(result.output).toMatch(/oklch\(\) color/)
|
|
expect(result.output).toMatch(/named color "red"/)
|
|
})
|
|
|
|
it('allows color-mix over tokens and neutral rgba shines', () => {
|
|
const result = runIn({
|
|
'tokens.css': TOKENS,
|
|
'a.tsx': `export const A = () => (
|
|
<div style={{
|
|
background: 'color-mix(in srgb, var(--primary) 12%, transparent)',
|
|
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.2)',
|
|
}} />
|
|
)\n`,
|
|
})
|
|
expect(result.code).toBe(0)
|
|
})
|
|
|
|
it('reads values split across lines in css', () => {
|
|
const result = runIn({
|
|
'tokens.css': TOKENS,
|
|
'b.css': `.b {\n background:\n linear-gradient(\n to right,\n crimson,\n navy\n );\n}\n`,
|
|
})
|
|
expect(result.code).toBe(1)
|
|
expect(result.output).toMatch(/named color "crimson"/)
|
|
})
|
|
|
|
it('flags px font sizes in css and tsx', () => {
|
|
const result = runIn({
|
|
'tokens.css': TOKENS,
|
|
'b.css': `.b { font-size: 14px; }\n`,
|
|
'a.tsx': `export const A = () => <div style={{ fontSize: '14px' }} />\n`,
|
|
})
|
|
expect(result.code).toBe(1)
|
|
expect(result.output.match(/px font size/g)).toHaveLength(2)
|
|
})
|
|
|
|
it('fails on var(--x) that no css file defines', () => {
|
|
const result = runIn({
|
|
'tokens.css': TOKENS,
|
|
'a.tsx': `export const A = () => <div style={{ color: 'var(--priamry)' }} />\n`,
|
|
})
|
|
expect(result.code).toBe(1)
|
|
expect(result.output).toMatch(/var\(--priamry\) is never defined/)
|
|
})
|
|
|
|
it('keeps the real repo clean', () => {
|
|
expect(() => execFileSync(process.execPath, [GUARD], { stdio: 'pipe' })).not.toThrow()
|
|
})
|
|
})
|