39 lines
1.7 KiB
TypeScript
39 lines
1.7 KiB
TypeScript
/// <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)
|
|
}
|