Skip to content

Commit 644ae7e

Browse files
authored
fix(theme): case-insensitive theme matching for streak and wrapped pages (JhaSourav07#2180)
## Description Fixes JhaSourav07#2032 This PR implements **Case-Insensitive Theme Preset Matching** across the CommitPulse schema validators and API routes. ## Pillar - [ ] 🎨 Pillar 1 — New Theme Design - [ ] 📐 Pillar 2 — Geometric SVG Improvement - [ ] 🕐 Pillar 3 — Timezone Logic Optimization - [x ] 🛠️ Other (Bug fix, refactoring, docs) ## Visual Preview Querying `?theme=NEON` or other capitalized variants now successfully maps to the gorgeous neon theme rather than failing registry lookup and falling back to default. <img width="1172" height="875" alt="image" src="https://github.com/user-attachments/assets/27bfa584-3de8-43ea-8c4f-7ed86bbf77b4" /> ## Checklist before requesting a review: - [x] I have read the `CONTRIBUTING.md` file. - [x] I have tested these changes locally (`localhost:3000/api/streak?user=YOUR_USERNAME`). - [x] I have run `npm run format` and `npm run lint` locally and resolved all errors (CI will fail otherwise). - [x] My commits follow the Conventional Commits format (e.g., `feat(themes): ...`, `fix(calculate): ...`). - [ ] I have updated `README.md` if I added a new theme or URL parameter. - [x] I have started the repo. - [x] I have made sure that i have only one commit to merge in this PR. - [x] The SVG output matches the CommitPulse "premium quality" aesthetic standard (no raw elements, smooth animations, correct fonts). - [ ] (Recommended) I joined the CommitPulse Discord community for contributor discussions, mentorship, and faster PR support.
2 parents fdd87db + 7f98fc3 commit 644ae7e

4 files changed

Lines changed: 54 additions & 47 deletions

File tree

.env.local.example

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@
2020
# Generate new token (classic)
2121
# Required scope: read:user
2222
# ------------------------------------------------------------
23-
GITHUB_TOKEN=ghp_your_personal_access_token_here
24-
23+
GITHUB_TOKEN=
2524

2625
# ------------------------------------------------------------
2726
# REQUIRED — Public site URL

app/api/streak/route.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,22 @@ describe('GET /api/streak', () => {
837837
expect(fieldError).toContain('light');
838838
expect(fieldError).toContain('neon');
839839
});
840+
841+
it('accepts capitalized or mixed-case theme parameter like "NEON" and maps it correctly', async () => {
842+
const response = await GET(makeRequest({ user: 'octocat', theme: 'NEON' }));
843+
const body = await response.text();
844+
845+
expect(response.status).toBe(200);
846+
expect(body).toContain('ff00ff'); // Neon theme accent is #ff00ff — confirms the neon theme is applied
847+
});
848+
849+
it('accepts mixed-case "random" or "auto" and resolves correctly', async () => {
850+
const response = await GET(makeRequest({ user: 'octocat', theme: 'aUtO' }));
851+
const body = await response.text();
852+
853+
expect(response.status).toBe(200);
854+
expect(body).toContain('prefers-color-scheme: dark');
855+
});
840856
});
841857

842858
describe('custom colour overrides', () => {

lib/validations.test.ts

Lines changed: 20 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,69 +1082,47 @@ describe('streakParamsSchema — layout query validation boundaries (Variation 2
10821082
});
10831083
});
10841084

1085-
/* ==========================================================================
1086-
* LAYOUT PARAMETER — QUERY VALIDATION BOUNDARIES (VARIATION 2)
1087-
* ========================================================================== */
1088-
1089-
describe('streakParamsSchema — layout query validation boundaries (Variation 2)', () => {
1090-
it('rejects unsupported_layout and marks the parse as failed', () => {
1085+
describe('streakParamsSchema — case-insensitive theme matching', () => {
1086+
it('accepts lowercase theme parameters', () => {
10911087
const result = streakParamsSchema.safeParse({
10921088
user: 'octocat',
1093-
layout: 'unsupported_layout',
1089+
theme: 'neon',
10941090
});
1095-
1096-
expect(result.success).toBe(false);
1097-
});
1098-
1099-
it('surfaces a meaningful error message for unsupported_layout', () => {
1100-
const result = streakParamsSchema.safeParse({
1101-
user: 'octocat',
1102-
layout: 'unsupported_layout',
1103-
});
1104-
1105-
expect(result.success).toBe(false);
1106-
if (!result.success) {
1107-
const messages = result.error.issues.map((i) => i.message).join(' ');
1108-
expect(messages).toContain('Invalid layout format');
1109-
}
1110-
});
1111-
1112-
it('accepts "default" as a valid layout value', () => {
1113-
const result = streakParamsSchema.safeParse({
1114-
user: 'octocat',
1115-
layout: 'default',
1116-
});
1117-
11181091
expect(result.success).toBe(true);
1092+
if (result.success) {
1093+
expect(result.data.theme).toBe('neon');
1094+
}
11191095
});
11201096

1121-
it('accepts "compact" as a valid layout value', () => {
1097+
it('accepts uppercase theme parameters and maps correctly', () => {
11221098
const result = streakParamsSchema.safeParse({
11231099
user: 'octocat',
1124-
layout: 'compact',
1100+
theme: 'NEON',
11251101
});
1126-
11271102
expect(result.success).toBe(true);
1103+
if (result.success) {
1104+
// Maps capitalized input back to lowercase registry key
1105+
expect(result.data.theme).toBe('neon');
1106+
}
11281107
});
11291108

1130-
it('accepts "full" as a valid layout value', () => {
1109+
it('accepts mixed-case theme parameters and maps correctly', () => {
11311110
const result = streakParamsSchema.safeParse({
11321111
user: 'octocat',
1133-
layout: 'full',
1112+
theme: 'DrAcUlA',
11341113
});
1135-
11361114
expect(result.success).toBe(true);
1115+
if (result.success) {
1116+
expect(result.data.theme).toBe('dracula');
1117+
}
11371118
});
11381119

1139-
it('treats omitted layout as undefined (no validation error)', () => {
1120+
it('rejects completely invalid theme name', () => {
11401121
const result = streakParamsSchema.safeParse({
11411122
user: 'octocat',
1123+
theme: 'fictionaltheme',
11421124
});
1143-
1144-
expect(result.success).toBe(true);
1145-
if (result.success) {
1146-
expect(result.data.layout).toBeUndefined();
1147-
}
1125+
expect(result.success).toBe(false);
11481126
});
11491127
});
11501128

lib/validations.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,13 @@ export function toEmptyStringAsUndefined(val?: string): string | undefined {
2323
}
2424

2525
export function toValidTheme(val?: string): string | undefined {
26-
return val && Object.hasOwn(themes, val) ? val : 'dark';
26+
if (!val) return 'dark';
27+
const normalized = val.toLowerCase();
28+
if (normalized === 'auto' || normalized === 'random') {
29+
return normalized;
30+
}
31+
const matchedKey = Object.keys(themes).find((key) => key.toLowerCase() === normalized);
32+
return matchedKey || 'dark';
2733
}
2834

2935
export function toValidHexColor(defaultColor: string) {
@@ -112,9 +118,17 @@ const baseStreakParamsSchema = z.object({
112118
theme: z
113119
.string()
114120
.optional()
121+
.transform((val) => {
122+
if (val === undefined || val === '') return 'dark';
123+
const normalized = val.toLowerCase();
124+
if (normalized === 'auto' || normalized === 'random') {
125+
return normalized;
126+
}
127+
const matchedKey = Object.keys(themes).find((key) => key.toLowerCase() === normalized);
128+
return matchedKey || val;
129+
})
115130
.refine(
116131
(val) => {
117-
if (val === undefined || val === '') return true;
118132
return val === 'auto' || val === 'random' || Object.hasOwn(themes, val);
119133
},
120134
{
@@ -446,7 +460,7 @@ export const wrappedParamsSchema = z.object({
446460
message: 'GitHub was founded in 2008. Please provide a year of 2008 or later.',
447461
}
448462
),
449-
theme: z.string().default('dark'),
463+
theme: z.string().optional().transform(toValidTheme).default('dark'),
450464
bg: z
451465
.string()
452466
.optional()

0 commit comments

Comments
 (0)