Skip to content

Commit fcc2671

Browse files
test(theme-dracula): add unit tests with WCAG contrast validation for dracula theme
1 parent 29dd950 commit fcc2671

2 files changed

Lines changed: 94 additions & 0 deletions

File tree

lib/svg/themes/dracula.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { themes } from '../themes';
3+
import { generateSVG } from '../generator';
4+
import type { BadgeParams, ContributionCalendar, StreakStats } from '../../../types';
5+
import { contrastRatio } from './test-utils';
6+
7+
describe('dracula theme', () => {
8+
it('exists as a key in the themes object', () => {
9+
expect(themes).toHaveProperty('dracula');
10+
});
11+
12+
it('has valid 6-digit hex color strings (without #) for bg, text, and accent', () => {
13+
const hexRegex = /^[0-9a-fA-F]{6}$/;
14+
15+
expect(themes.dracula.bg).toMatch(hexRegex);
16+
expect(themes.dracula.text).toMatch(hexRegex);
17+
expect(themes.dracula.accent).toMatch(hexRegex);
18+
});
19+
20+
it('matches the defined dracula color values for the design spec', () => {
21+
expect(themes.dracula.bg).toBe('282a36');
22+
expect(themes.dracula.text).toBe('f8f8f2');
23+
expect(themes.dracula.accent).toBe('bd93f9');
24+
});
25+
26+
it('contains the specific dracula hex colors in generated SVG output', () => {
27+
const mockStats: StreakStats = {
28+
currentStreak: 5,
29+
longestStreak: 10,
30+
totalContributions: 100,
31+
todayDate: '2024-06-12',
32+
};
33+
const mockCalendar: ContributionCalendar = {
34+
totalContributions: 10,
35+
weeks: [
36+
{
37+
contributionDays: [
38+
{ contributionCount: 5, date: '2024-06-11' },
39+
{ contributionCount: 5, date: '2024-06-12' },
40+
],
41+
},
42+
],
43+
};
44+
const draculaParams: BadgeParams = {
45+
user: 'testuser',
46+
bg: themes.dracula.bg,
47+
text: themes.dracula.text,
48+
accent: themes.dracula.accent,
49+
speed: '8s',
50+
scale: 'linear',
51+
};
52+
53+
const svg = generateSVG(mockStats, draculaParams, mockCalendar);
54+
55+
expect(svg).toContain(`#${themes.dracula.bg}`);
56+
expect(svg).toContain(`#${themes.dracula.text}`);
57+
expect(svg).toContain(`#${themes.dracula.accent}`);
58+
});
59+
60+
it('provides sufficient WCAG AA contrast between background and text', () => {
61+
const ratio = contrastRatio(themes.dracula.bg, themes.dracula.text);
62+
expect(ratio).toBeGreaterThanOrEqual(4.5);
63+
});
64+
});

lib/svg/themes/test-utils.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
export function hexToRgb(hex: string): { r: number; g: number; b: number } {
2+
const normalized = hex.replace(/^#/, '');
3+
if (!/^[0-9a-fA-F]{6}$/.test(normalized)) {
4+
throw new Error(
5+
`Invalid hex color: "${hex}". Expected 6-digit hex string (with or without #).`
6+
);
7+
}
8+
return {
9+
r: parseInt(normalized.slice(0, 2), 16),
10+
g: parseInt(normalized.slice(2, 4), 16),
11+
b: parseInt(normalized.slice(4, 6), 16),
12+
};
13+
}
14+
15+
export function relativeLuminance(hex: string): number {
16+
const { r, g, b } = hexToRgb(hex);
17+
const [rl, gl, bl] = [r, g, b].map((c) => {
18+
const s = c / 255;
19+
return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
20+
});
21+
return 0.2126 * rl + 0.7152 * gl + 0.0722 * bl;
22+
}
23+
24+
export function contrastRatio(bg: string, text: string): number {
25+
const lBg = relativeLuminance(bg);
26+
const lText = relativeLuminance(text);
27+
const lighter = Math.max(lBg, lText);
28+
const darker = Math.min(lBg, lText);
29+
return (lighter + 0.05) / (darker + 0.05);
30+
}

0 commit comments

Comments
 (0)