Skip to content

Commit 4538996

Browse files
JhaSourav07ChetanSenta
andauthored
test(generator): add Vitest coverage for neon theme, accent override, border, entity title, and generateVersusSVG (JhaSourav07#1608)
* test(generator): add Vitest tests for neon theme, accent override, border param, org/repo title entity, and generateVersusSVG coverage * Revert "test(generator): add Vitest tests for neon theme, accent override, border param, org/repo title entity, and generateVersusSVG coverage" This reverts commit a040b79. * test(generator): add Vitest tests for neon theme, accent override, border param, org/repo title entity, and generateVersusSVG coverage --------- Co-authored-by: ChetanSenta <chetansenta11@gamil.com>
2 parents 8d603de + 4124733 commit 4538996

1 file changed

Lines changed: 332 additions & 0 deletions

File tree

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
// lib/svg/generator.test.new.ts
2+
// New tests covering gaps in existing generator.test.ts coverage.
3+
// Covers: generateVersusSVG, neon theme bg, accent override, border param, org/repo title entity.
4+
5+
import { describe, it, expect } from 'vitest';
6+
import { generateSVG, generateVersusSVG } from './generator';
7+
import type { BadgeParams, ContributionCalendar, StreakStats } from '../../types';
8+
import { hexColor } from './sanitizer';
9+
10+
// ─── Shared fixtures ──────────────────────────────────────────────────────────
11+
12+
const baseStats: StreakStats = {
13+
currentStreak: 7,
14+
longestStreak: 14,
15+
totalContributions: 200,
16+
todayDate: '2024-06-12',
17+
};
18+
19+
const baseCalendar: ContributionCalendar = {
20+
totalContributions: 200,
21+
weeks: [
22+
{
23+
contributionDays: [
24+
{ contributionCount: 3, date: '2024-06-10' },
25+
{ contributionCount: 8, date: '2024-06-11' },
26+
{ contributionCount: 12, date: '2024-06-12' },
27+
],
28+
},
29+
],
30+
};
31+
32+
// ─── 1. Neon theme background color ──────────────────────────────────────────
33+
34+
describe('[Issue] neon theme bg color in SVG output', () => {
35+
it('renders #000000 background when theme=neon bg is passed directly', () => {
36+
// The neon theme has bg: '000000' — pass it as the bg param
37+
const svg = generateSVG(
38+
baseStats,
39+
{
40+
user: 'chetan',
41+
bg: hexColor('000000'), // neon theme bg
42+
text: hexColor('00ffcc'), // neon theme text
43+
accent: hexColor('ff00ff'), // neon theme accent
44+
speed: '8s',
45+
scale: 'linear',
46+
} satisfies BadgeParams,
47+
baseCalendar
48+
);
49+
50+
// Background rect must use the neon bg color
51+
expect(svg).toContain('fill="#000000"');
52+
// Accent color must appear on tower fills
53+
expect(svg).toContain('#ff00ff');
54+
});
55+
56+
it('neon bg color #000000 appears in the SVG fill attribute', () => {
57+
const svg = generateSVG(
58+
baseStats,
59+
{
60+
user: 'chetan',
61+
bg: hexColor('000000'),
62+
text: hexColor('00ffcc'),
63+
accent: hexColor('ff00ff'),
64+
speed: '8s',
65+
scale: 'linear',
66+
} satisfies BadgeParams,
67+
baseCalendar
68+
);
69+
70+
// The rect background fill should be the neon bg
71+
const match = svg.match(/fill="#000000"/);
72+
expect(match).not.toBeNull();
73+
});
74+
});
75+
76+
// ─── 2. Custom accent overrides theme tower fill color ────────────────────────
77+
78+
describe('[Issue] custom accent param overrides tower fill color', () => {
79+
it('uses custom accent hex in tower fill attributes', () => {
80+
const customAccent = 'ff4500'; // custom orange, not any theme default
81+
82+
const svg = generateSVG(
83+
baseStats,
84+
{
85+
user: 'chetan',
86+
bg: hexColor('0d1117'),
87+
text: hexColor('ffffff'),
88+
accent: hexColor(customAccent),
89+
speed: '8s',
90+
scale: 'linear',
91+
} satisfies BadgeParams,
92+
baseCalendar
93+
);
94+
95+
// The custom accent color should appear in the SVG as tower fill
96+
expect(svg).toContain(`#${customAccent}`);
97+
});
98+
99+
it('different accent values produce different SVG fill colors', () => {
100+
const svgBlue = generateSVG(
101+
baseStats,
102+
{
103+
user: 'chetan',
104+
bg: hexColor('0d1117'),
105+
text: hexColor('ffffff'),
106+
accent: hexColor('0000ff'),
107+
speed: '8s',
108+
scale: 'linear',
109+
} satisfies BadgeParams,
110+
baseCalendar
111+
);
112+
113+
const svgRed = generateSVG(
114+
baseStats,
115+
{
116+
user: 'chetan',
117+
bg: hexColor('0d1117'),
118+
text: hexColor('ffffff'),
119+
accent: hexColor('ff0000'),
120+
speed: '8s',
121+
scale: 'linear',
122+
} satisfies BadgeParams,
123+
baseCalendar
124+
);
125+
126+
// Both SVGs should contain their respective accent colors
127+
expect(svgBlue).toContain('#0000ff');
128+
expect(svgRed).toContain('#ff0000');
129+
// And must NOT contain each other's accent
130+
expect(svgBlue).not.toContain('#ff0000');
131+
expect(svgRed).not.toContain('#0000ff');
132+
});
133+
});
134+
135+
// ─── 3. border parameter produces stroke attribute ────────────────────────────
136+
137+
describe('[Issue] border parameter produces stroke attribute in SVG', () => {
138+
it('adds stroke attribute to root rect when border is provided', () => {
139+
const svg = generateSVG(
140+
baseStats,
141+
{
142+
user: 'chetan',
143+
bg: hexColor('0d1117'),
144+
text: hexColor('ffffff'),
145+
accent: hexColor('58a6ff'),
146+
border: hexColor('ff00ff'), // neon pink border
147+
speed: '8s',
148+
scale: 'linear',
149+
} satisfies BadgeParams,
150+
baseCalendar
151+
);
152+
153+
expect(svg).toContain('stroke="#ff00ff"');
154+
expect(svg).toContain('stroke-width="2"');
155+
});
156+
157+
it('does not include stroke attribute when border is not provided', () => {
158+
const svg = generateSVG(
159+
baseStats,
160+
{
161+
user: 'chetan',
162+
bg: hexColor('0d1117'),
163+
text: hexColor('ffffff'),
164+
accent: hexColor('58a6ff'),
165+
speed: '8s',
166+
scale: 'linear',
167+
} satisfies BadgeParams,
168+
baseCalendar
169+
);
170+
171+
// No border param means no stroke on the background rect
172+
expect(svg).not.toContain('stroke="#');
173+
});
174+
});
175+
176+
// ─── 4. org / repo change entity type in <title> ─────────────────────────────
177+
178+
describe('[Issue] org and repo params change entity type in SVG <title>', () => {
179+
it('renders "User Stats" in title when neither org nor repo is set', () => {
180+
const svg = generateSVG(baseStats, { user: 'chetan' } as unknown as BadgeParams, baseCalendar);
181+
182+
expect(svg).toContain('<title>CommitPulse User Stats for chetan</title>');
183+
});
184+
185+
it('renders "Organization Stats" in title when org param is set', () => {
186+
const svg = generateSVG(
187+
baseStats,
188+
{ user: 'chetan', org: 'my-org' } as unknown as BadgeParams,
189+
baseCalendar
190+
);
191+
192+
expect(svg).toContain('<title>CommitPulse Organization Stats for chetan</title>');
193+
});
194+
195+
it('renders "Repository Stats" in title when repo param is set', () => {
196+
const svg = generateSVG(
197+
baseStats,
198+
{ user: 'chetan', repo: 'my-repo' } as unknown as BadgeParams,
199+
baseCalendar
200+
);
201+
202+
expect(svg).toContain('<title>CommitPulse Repository Stats for chetan</title>');
203+
});
204+
});
205+
206+
// ─── 5. generateVersusSVG — completely untested function ─────────────────────
207+
208+
describe('[Issue] generateVersusSVG — zero existing test coverage', () => {
209+
const stats1: StreakStats = {
210+
currentStreak: 5,
211+
longestStreak: 10,
212+
totalContributions: 120,
213+
todayDate: '2024-06-12',
214+
};
215+
216+
const stats2: StreakStats = {
217+
currentStreak: 3,
218+
longestStreak: 8,
219+
totalContributions: 80,
220+
todayDate: '2024-06-12',
221+
};
222+
223+
const calendar1: ContributionCalendar = {
224+
totalContributions: 120,
225+
weeks: [
226+
{
227+
contributionDays: [
228+
{ contributionCount: 4, date: '2024-06-10' },
229+
{ contributionCount: 9, date: '2024-06-11' },
230+
],
231+
},
232+
],
233+
};
234+
235+
const calendar2: ContributionCalendar = {
236+
totalContributions: 80,
237+
weeks: [
238+
{
239+
contributionDays: [
240+
{ contributionCount: 2, date: '2024-06-10' },
241+
{ contributionCount: 6, date: '2024-06-11' },
242+
],
243+
},
244+
],
245+
};
246+
247+
const versusParams: BadgeParams = {
248+
user: 'chetan',
249+
versus: 'rival',
250+
bg: hexColor('0d1117'),
251+
text: hexColor('ffffff'),
252+
accent: hexColor('58a6ff'),
253+
speed: '8s',
254+
scale: 'linear',
255+
};
256+
257+
it('produces a valid SVG string (starts with <svg, ends with </svg>)', () => {
258+
const svg = generateVersusSVG(stats1, stats2, versusParams, calendar1, calendar2);
259+
260+
expect(svg.trim()).toBeDefined();
261+
expect(svg).toContain('<svg');
262+
expect(svg).toContain('</svg>');
263+
});
264+
265+
it('includes role="img" for accessibility', () => {
266+
const svg = generateVersusSVG(stats1, stats2, versusParams, calendar1, calendar2);
267+
expect(svg).toContain('role="img"');
268+
});
269+
270+
it('includes both usernames in the SVG <title> tag', () => {
271+
const svg = generateVersusSVG(stats1, stats2, versusParams, calendar1, calendar2);
272+
expect(svg).toContain('chetan');
273+
expect(svg).toContain('rival');
274+
expect(svg).toContain('CommitPulse Versus Stats');
275+
});
276+
277+
it('renders a "VS" label between the two panels', () => {
278+
const svg = generateVersusSVG(stats1, stats2, versusParams, calendar1, calendar2);
279+
expect(svg).toContain('>VS<');
280+
});
281+
282+
it('renders a dividing line between the two user panels', () => {
283+
const svg = generateVersusSVG(stats1, stats2, versusParams, calendar1, calendar2);
284+
// The dashed vertical divider line
285+
expect(svg).toContain('stroke-dasharray="4 4"');
286+
});
287+
288+
it('total width is double the single card width for medium size', () => {
289+
const svg = generateVersusSVG(stats1, stats2, versusParams, calendar1, calendar2);
290+
// Medium size: SVG_WIDTH=600, versus = 600*2=1200
291+
expect(svg).toContain('width="1200"');
292+
});
293+
294+
it('includes total contribution counts from both users in <desc>', () => {
295+
const svg = generateVersusSVG(stats1, stats2, versusParams, calendar1, calendar2);
296+
expect(svg).toContain('120');
297+
expect(svg).toContain('80');
298+
});
299+
300+
it('escapes XML characters in both usernames', () => {
301+
const svg = generateVersusSVG(
302+
stats1,
303+
stats2,
304+
{ ...versusParams, user: 'chetan&dev', versus: 'rival<one>' },
305+
calendar1,
306+
calendar2
307+
);
308+
309+
expect(svg).toContain('chetan&amp;dev');
310+
expect(svg).toContain('rival&lt;one&gt;');
311+
expect(svg).not.toContain('chetan&dev');
312+
expect(svg).not.toContain('rival<one>');
313+
});
314+
315+
it('generates auto-theme versus SVG with CSS custom properties', () => {
316+
const autoVersusParams: BadgeParams = {
317+
...versusParams,
318+
autoTheme: true,
319+
};
320+
321+
const svg = generateVersusSVG(stats1, stats2, autoVersusParams, calendar1, calendar2);
322+
323+
expect(svg).toContain('--cp-bg');
324+
expect(svg).toContain('--cp-accent');
325+
expect(svg).toContain('prefers-color-scheme: dark');
326+
});
327+
328+
it('does not render an auto-theme media query for static versus SVG', () => {
329+
const svg = generateVersusSVG(stats1, stats2, versusParams, calendar1, calendar2);
330+
expect(svg).not.toContain('prefers-color-scheme: dark');
331+
});
332+
});

0 commit comments

Comments
 (0)