Skip to content

Commit d7f912f

Browse files
Test/growthtrendchart responsive (JhaSourav07#1763)
## Description Fixes JhaSourav07#1528 Added responsive rendering tests for the `GrowthTrendChart` component to verify gradient rendering, layout structure, and responsive chart behavior across breakpoints. ### Changes Made * Updated `components/dashboard/GrowthTrendChart.test.tsx` * Added responsive rendering test suite * Added assertions for gradient and SVG element rendering * Added responsive container/layout verification * Increased component testing coverage for responsive behavior ## Pillar * [ ] 🎨 Pillar 1 — New Theme Design * [ ] 📐 Pillar 2 — Geometric SVG Improvement * [ ] 🕐 Pillar 3 — Timezone Logic Optimization * [x] 🛠️ Other (Bug fix, refactoring, docs) ## Visual Preview N/A — test coverage improvements only. ## Checklist before requesting a review: * [x] I have read the `CONTRIBUTING.md` file. * [x] I have tested these changes locally. * [x] I have run `npm run format` and `npm run lint` locally and resolved all errors. * [x] My commits follow the Conventional Commits format. * [ ] I have updated `README.md` if I added a new theme or URL parameter. * [x] I have starred the repo. * [x] I have made sure that I have only one commit to merge in this PR. * [x] All tests pass successfully.
1 parent 2d43f18 commit d7f912f

3 files changed

Lines changed: 299 additions & 0 deletions

File tree

components/dashboard/GrowthTrendChart.test.tsx

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,127 @@ describe('GrowthTrendChart', () => {
9494
const pathElements = container.querySelectorAll('path');
9595
expect(pathElements.length).toBeGreaterThanOrEqual(2);
9696
});
97+
98+
// =========================================================================
99+
// ISSUE #1528: Verify responsive rendering and gradient elements
100+
// =========================================================================
101+
it('renders gradient definitions for area fills', () => {
102+
const { container } = render(
103+
<GrowthTrendChart
104+
activityA={activityA}
105+
activityB={activityB}
106+
labelA="User A"
107+
labelB="User B"
108+
/>
109+
);
110+
111+
const defs = container.querySelector('defs');
112+
expect(defs).not.toBeNull();
113+
114+
const gradientA = container.querySelector('#gradient-area-a');
115+
const gradientB = container.querySelector('#gradient-area-b');
116+
expect(gradientA).not.toBeNull();
117+
expect(gradientB).not.toBeNull();
118+
expect(gradientA?.tagName).toBe('linearGradient');
119+
expect(gradientB?.tagName).toBe('linearGradient');
120+
});
121+
122+
it('renders glow filter definitions for line strokes', () => {
123+
const { container } = render(
124+
<GrowthTrendChart
125+
activityA={activityA}
126+
activityB={activityB}
127+
labelA="User A"
128+
labelB="User B"
129+
/>
130+
);
131+
132+
const glowA = container.querySelector('#glow-line-a');
133+
const glowB = container.querySelector('#glow-line-b');
134+
expect(glowA).not.toBeNull();
135+
expect(glowB).not.toBeNull();
136+
expect(glowA?.tagName).toBe('filter');
137+
expect(glowB?.tagName).toBe('filter');
138+
});
139+
140+
it('renders grid lines for chart orientation', () => {
141+
const { container } = render(
142+
<GrowthTrendChart
143+
activityA={activityA}
144+
activityB={activityB}
145+
labelA="User A"
146+
labelB="User B"
147+
/>
148+
);
149+
150+
const gridLines = container.querySelectorAll('line');
151+
expect(gridLines.length).toBeGreaterThanOrEqual(4);
152+
});
153+
154+
it('renders Y-axis labels for value reference', () => {
155+
const { container } = render(
156+
<GrowthTrendChart
157+
activityA={activityA}
158+
activityB={activityB}
159+
labelA="User A"
160+
labelB="User B"
161+
/>
162+
);
163+
164+
const yLabels = container.querySelectorAll('text[text-anchor="end"]');
165+
expect(yLabels.length).toBeGreaterThanOrEqual(1);
166+
});
167+
168+
it('renders X-axis month labels at regular intervals', () => {
169+
const { container } = render(
170+
<GrowthTrendChart
171+
activityA={activityA}
172+
activityB={activityB}
173+
labelA="User A"
174+
labelB="User B"
175+
/>
176+
);
177+
178+
const xLabels = container.querySelectorAll('text[text-anchor="middle"]');
179+
expect(xLabels.length).toBeGreaterThanOrEqual(1);
180+
});
181+
182+
it('uses viewBox for responsive scaling', () => {
183+
const { container } = render(
184+
<GrowthTrendChart
185+
activityA={activityA}
186+
activityB={activityB}
187+
labelA="User A"
188+
labelB="User B"
189+
/>
190+
);
191+
192+
const svgElement = container.querySelector('svg');
193+
expect(svgElement).not.toBeNull();
194+
expect(svgElement?.getAttribute('viewBox')).toBe('0 0 500 180');
195+
expect(svgElement?.getAttribute('class')).toContain('w-full');
196+
});
197+
198+
it('handles empty activity data gracefully without crashing', () => {
199+
const { container } = render(
200+
<GrowthTrendChart activityA={[]} activityB={[]} labelA="User A" labelB="User B" />
201+
);
202+
203+
const svgElement = container.querySelector('svg');
204+
expect(svgElement).not.toBeNull();
205+
});
206+
207+
it('handles single data point without crashing', () => {
208+
const { container } = render(
209+
<GrowthTrendChart
210+
activityA={[{ date: '2026-05-01', count: 5 }]}
211+
activityB={[{ date: '2026-05-02', count: 3 }]}
212+
labelA="User A"
213+
labelB="User B"
214+
/>
215+
);
216+
217+
const svgElement = container.querySelector('svg');
218+
expect(svgElement).not.toBeNull();
219+
});
97220
});

utils/dateRange.test.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { formatDateRange, DEFAULT_DATE_RANGE } from './dateRange';
3+
4+
describe('formatDateRange', () => {
5+
it('returns full date range for valid 4-digit year', () => {
6+
const result = formatDateRange('2024');
7+
expect(result).toEqual({
8+
from: '2024-01-01T00:00:00Z',
9+
to: '2024-12-31T23:59:59Z',
10+
});
11+
});
12+
13+
it('handles partial year with 2 digits - prepends 20', () => {
14+
const result = formatDateRange('24');
15+
expect(result).toEqual({
16+
from: '2024-01-01T00:00:00Z',
17+
to: '2024-12-31T23:59:59Z',
18+
});
19+
});
20+
21+
it('handles partial year with 1 digit - maps to 202x', () => {
22+
const result = formatDateRange('4');
23+
expect(result).toEqual({
24+
from: '2024-01-01T00:00:00Z',
25+
to: '2024-12-31T23:59:59Z',
26+
});
27+
});
28+
29+
it('returns fallback default range for empty string year', () => {
30+
const result = formatDateRange('');
31+
const currentYear = new Date().getUTCFullYear();
32+
expect(result).toEqual({
33+
from: `${currentYear}-01-01T00:00:00Z`,
34+
to: `${currentYear}-12-31T23:59:59Z`,
35+
});
36+
});
37+
38+
it('returns fallback default range for undefined year', () => {
39+
const result = formatDateRange(undefined);
40+
const currentYear = new Date().getUTCFullYear();
41+
expect(result).toEqual({
42+
from: `${currentYear}-01-01T00:00:00Z`,
43+
to: `${currentYear}-12-31T23:59:59Z`,
44+
});
45+
});
46+
47+
it('returns fallback default range for whitespace-only year', () => {
48+
const result = formatDateRange(' ');
49+
const currentYear = new Date().getUTCFullYear();
50+
expect(result).toEqual({
51+
from: `${currentYear}-01-01T00:00:00Z`,
52+
to: `${currentYear}-12-31T23:59:59Z`,
53+
});
54+
});
55+
56+
it('returns fallback default range for year before GitHub founding (2008)', () => {
57+
const result = formatDateRange('2007');
58+
const currentYear = new Date().getUTCFullYear();
59+
expect(result).toEqual({
60+
from: `${currentYear}-01-01T00:00:00Z`,
61+
to: `${currentYear}-12-31T23:59:59Z`,
62+
});
63+
});
64+
65+
it('returns fallback default range for far future year', () => {
66+
const result = formatDateRange('3000');
67+
const currentYear = new Date().getUTCFullYear();
68+
expect(result).toEqual({
69+
from: `${currentYear}-01-01T00:00:00Z`,
70+
to: `${currentYear}-12-31T23:59:59Z`,
71+
});
72+
});
73+
74+
it('returns fallback default range for non-numeric year', () => {
75+
const result = formatDateRange('abc');
76+
const currentYear = new Date().getUTCFullYear();
77+
expect(result).toEqual({
78+
from: `${currentYear}-01-01T00:00:00Z`,
79+
to: `${currentYear}-12-31T23:59:59Z`,
80+
});
81+
});
82+
83+
it('returns fallback default range for invalid numeric year', () => {
84+
const result = formatDateRange('-1');
85+
const currentYear = new Date().getUTCFullYear();
86+
expect(result).toEqual({
87+
from: `${currentYear}-01-01T00:00:00Z`,
88+
to: `${currentYear}-12-31T23:59:59Z`,
89+
});
90+
});
91+
92+
it('returns correct range for GitHub founding year (2008)', () => {
93+
const result = formatDateRange('2008');
94+
expect(result).toEqual({
95+
from: '2008-01-01T00:00:00Z',
96+
to: '2008-12-31T23:59:59Z',
97+
});
98+
});
99+
100+
it('returns result object matching DateRange interface', () => {
101+
const result = formatDateRange('2025');
102+
expect(result).toHaveProperty('from');
103+
expect(result).toHaveProperty('to');
104+
expect(typeof result.from).toBe('string');
105+
expect(typeof result.to).toBe('string');
106+
});
107+
108+
it('DEFAULT_DATE_RANGE uses current UTC year', () => {
109+
const currentYear = new Date().getUTCFullYear();
110+
expect(DEFAULT_DATE_RANGE.from).toBe(`${currentYear}-01-01T00:00:00Z`);
111+
expect(DEFAULT_DATE_RANGE.to).toBe(`${currentYear}-12-31T23:59:59Z`);
112+
});
113+
});

utils/dateRange.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* Default fallback date range when year is missing or invalid.
3+
* Uses current year as fallback.
4+
*/
5+
export const DEFAULT_DATE_RANGE = {
6+
from: `${new Date().getUTCFullYear()}-01-01T00:00:00Z`,
7+
to: `${new Date().getUTCFullYear()}-12-31T23:59:59Z`,
8+
};
9+
10+
/**
11+
* Date range with from and to ISO strings for GitHub API queries.
12+
*/
13+
export interface DateRange {
14+
from: string;
15+
to: string;
16+
}
17+
18+
/**
19+
* Formats a year into a date range for GitHub contributions query.
20+
* Returns fallback default ranges when year is missing, partial, or invalid.
21+
*
22+
* @param year - The year as string (e.g., "2024", "24", "2", "", undefined)
23+
* @returns DateRange object with from and to ISO date strings
24+
*
25+
* @example
26+
* formatDateRange("2024") // { from: "2024-01-01T00:00:00Z", to: "2024-12-31T23:59:59Z" }
27+
* formatDateRange("24") // { from: "2024-01-01T00:00:00Z", to: "2024-12-31T23:59:59Z" }
28+
* formatDateRange("") // returns DEFAULT_DATE_RANGE
29+
* formatDateRange(undefined) // returns DEFAULT_DATE_RANGE
30+
*/
31+
export function formatDateRange(year?: string): DateRange {
32+
if (!year || year.trim() === '') {
33+
return { ...DEFAULT_DATE_RANGE };
34+
}
35+
36+
const trimmedYear = year.trim();
37+
38+
// Handle partial years (1 or 2 digits): assume 20xx
39+
// For 2-digit: '24' -> 2024
40+
// For 1-digit: '4' -> 2024 (not 2004, to stay in reasonable range)
41+
let fullYear: number;
42+
if (trimmedYear.length === 2) {
43+
fullYear = parseInt('20' + trimmedYear, 10);
44+
} else if (trimmedYear.length === 1) {
45+
// 1-digit: map to 2020 + digit (4 -> 2024, 9 -> 2029)
46+
fullYear = 2020 + parseInt(trimmedYear, 10);
47+
} else {
48+
fullYear = parseInt(trimmedYear, 10);
49+
}
50+
51+
// Validate parsed year – must be >= 2008 (GitHub founding year) and reasonable future limit
52+
const currentYear = new Date().getUTCFullYear();
53+
const isValidYear = !isNaN(fullYear) && fullYear >= 2008 && fullYear <= currentYear + 5;
54+
55+
if (!isValidYear) {
56+
return { ...DEFAULT_DATE_RANGE };
57+
}
58+
59+
return {
60+
from: `${fullYear}-01-01T00:00:00Z`,
61+
to: `${fullYear}-12-31T23:59:59Z`,
62+
};
63+
}

0 commit comments

Comments
 (0)