first commit

This commit is contained in:
baozaotumao2025
2026-07-18 21:10:39 +08:00
commit 1b90e552a5
91 changed files with 9056 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
/// <reference types="node" />
import { readFileSync } from 'node:fs'
import { describe, expect, it } from 'vitest'
import { themes } from '../../../src/design/theme'
const css = readFileSync('src/styles.css', 'utf8')
const guidelines = readFileSync('docs/ui-design.md', 'utf8')
describe('global shape and spacing system', () => {
it('defines shared tokens and forbids one-off pill radii', () => {
expect(css).toContain('--radius-control: 8px')
expect(css).toContain('--radius-surface: 16px')
expect(css).toContain('--space-card: 24px')
expect(css).toContain('--space-section: 24px')
expect(css).not.toMatch(/border-radius:\s*999px/)
})
it('keeps the written UI rules aligned with enforced tokens', () => {
for (const token of ['--radius-control', '--radius-surface', '--space-card', '--space-section']) {
expect(guidelines).toContain(token)
}
expect(guidelines).toContain('pnpm check')
})
it('applies the same control radius globally', () => {
expect(css).toMatch(/\.ant-tag[^}]+border-radius:\s*var\(--radius-control\)/)
expect(themes.blue.token?.borderRadius).toBe(8)
expect(themes.light.token?.borderRadius).toBe(8)
})
it('gives result content consistent breathing room', () => {
expect(css).toMatch(/\.result-card\s*>\s*\.ant-card-body[^}]+gap:\s*var\(--space-section\)/)
expect(css).toMatch(/\.result-columns[^}]+gap:\s*var\(--space-section\)/)
expect(css).toMatch(/\.result-columns section[^}]+padding:\s*var\(--space-card\)/)
expect(css).toMatch(/\.result-verdict\s*\{[^}]*padding:\s*0 7px !important/)
})
})
+38
View File
@@ -0,0 +1,38 @@
/// <reference types="node" />
import { readFileSync } from 'node:fs'
import { describe, expect, it } from 'vitest'
import { themes } from '../../../src/design/theme'
const css = readFileSync('src/styles.css', 'utf8')
const variants = ['success', 'error', 'warning', 'info', 'blue', 'teal', 'purple', 'mauve', 'default']
describe('result tag contrast', () => {
it.each(['blue', 'light'])('%s theme meets WCAG AA for every tag', (theme) => {
const block = css.match(new RegExp(`:root\\[data-theme="${theme}"\\] \\{([^}]+)`))?.[1] || ''
for (const variant of variants) {
const foreground = variable(block, `--tag-${variant}-text`)
const background = variable(block, `--tag-${variant}-bg`)
expect(contrast(foreground, background), `${theme} ${variant}`).toBeGreaterThanOrEqual(4.5)
expect(css).toContain(`.result-tag-${variant}`)
}
})
it.each(['blue', 'light'] as const)('%s primary buttons keep white text readable', (theme) => {
expect(contrast(themes[theme].token!.colorPrimary as string, '#ffffff')).toBeGreaterThanOrEqual(4.5)
})
})
function variable(block: string, name: string) {
const value = block.match(new RegExp(`${name}:\\s*(#[0-9a-f]{6})`, 'i'))?.[1]
expect(value, name).toBeTruthy()
return value!
}
function contrast(a: string, b: string) {
const luminance = (hex: string) => {
const channels = hex.slice(1).match(/.{2}/g)!.map((value) => parseInt(value, 16) / 255).map((value) => value <= .04045 ? value / 12.92 : ((value + .055) / 1.055) ** 2.4)
return .2126 * channels[0] + .7152 * channels[1] + .0722 * channels[2]
}
const [lighter, darker] = [luminance(a), luminance(b)].sort((x, y) => y - x)
return (lighter + .05) / (darker + .05)
}
+21
View File
@@ -0,0 +1,21 @@
import { theme } from 'antd'
import { describe, expect, it } from 'vitest'
import { getInitialTheme, setTheme, themes } from '../../../src/design/theme'
describe('theme registry', () => {
it('uses Ant Design dark rendering for the blue scheme', () => {
expect(themes.blue.algorithm).toBe(theme.darkAlgorithm)
})
it('registers the blue and light visual schemes', () => {
expect(Object.keys(themes)).toEqual(['blue', 'light'])
})
it('persists a valid theme and ignores invalid stored values', () => {
setTheme('light')
expect(document.documentElement.dataset.theme).toBe('light')
expect(getInitialTheme()).toBe('light')
localStorage.setItem('safety-theme', 'unknown')
expect(getInitialTheme()).toBe('blue')
})
})