Skip to content

Commit 012819c

Browse files
authored
test: add empty fallback edge case tests for ContributorsPage (JhaSourav07#2989)
## Description Fixes JhaSourav07#2853 Adds focused test coverage for ContributorsPage empty and fallback states. ### Added Tests * Verifies fallback UI renders when contributors data is empty * Verifies page layout remains stable in empty state * Verifies no contributor cards are rendered when the dataset is empty * Verifies fetch failures fall back safely * Verifies fallback rendering does not produce console errors ## 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-only changes) ## Checklist before requesting a review: * [x] I have read the `CONTRIBUTING.md` file. * [ ] I have tested these changes locally (`localhost:3000/api/streak?user=YOUR_USERNAME`). * [ ] 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. * [ ] I have made sure that i have only one commit to merge in this PR. * [ ] The SVG output matches the CommitPulse "premium quality" aesthetic standard (no raw elements, smooth animations, correct fonts). * [x] (Recommended) I joined the CommitPulse Discord community for contributor discussions, mentorship, and faster PR support.
2 parents 4861a02 + bcba828 commit 012819c

1 file changed

Lines changed: 137 additions & 0 deletions

File tree

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import { beforeEach, describe, expect, it, vi } from 'vitest';
3+
import { render, screen } from '@testing-library/react';
4+
import ContributorsPage from './page';
5+
6+
vi.mock('next/image', () => ({
7+
__esModule: true,
8+
default: (props: any) => <img {...props} />,
9+
}));
10+
11+
vi.mock('next/link', () => ({
12+
__esModule: true,
13+
default: ({ children, href, ...props }: any) => (
14+
<a href={href} {...props}>
15+
{children}
16+
</a>
17+
),
18+
}));
19+
20+
vi.mock('gsap', () => {
21+
const tween = { kill: vi.fn() };
22+
const mockGsap = {
23+
registerPlugin: vi.fn(),
24+
to: vi.fn().mockReturnValue(tween),
25+
fromTo: vi.fn().mockReturnValue(tween),
26+
set: vi.fn(),
27+
context: vi.fn((callback: any) => {
28+
if (typeof callback === 'function') {
29+
callback();
30+
}
31+
return { revert: vi.fn() };
32+
}),
33+
};
34+
return { default: mockGsap, gsap: mockGsap };
35+
});
36+
37+
vi.mock('gsap/ScrollTrigger', () => ({
38+
ScrollTrigger: {
39+
getAll: vi.fn(() => []),
40+
},
41+
}));
42+
43+
vi.mock('framer-motion', () => ({
44+
motion: {
45+
div: 'div',
46+
span: 'span',
47+
p: 'p',
48+
h1: 'h1',
49+
h2: 'h2',
50+
h3: 'h3',
51+
h4: 'h4',
52+
h5: 'h5',
53+
h6: 'h6',
54+
section: 'section',
55+
a: 'a',
56+
button: 'button',
57+
img: 'img',
58+
},
59+
AnimatePresence: ({ children }: any) => <>{children}</>,
60+
useMotionValue: (initial: any) => ({ current: initial, set: vi.fn() }),
61+
useSpring: (value: any) => value,
62+
useTransform: (value: any, fn: any) => fn(value.current ?? value),
63+
}));
64+
65+
describe('ContributorsPage empty fallback', () => {
66+
beforeEach(() => {
67+
vi.restoreAllMocks();
68+
69+
global.fetch = vi.fn(() =>
70+
Promise.resolve({
71+
ok: true,
72+
json: async () => [],
73+
})
74+
) as any;
75+
76+
window.HTMLElement.prototype.scrollIntoView = vi.fn();
77+
78+
if (!window.requestAnimationFrame) {
79+
window.requestAnimationFrame = (callback: FrameRequestCallback) =>
80+
setTimeout(callback, 0) as unknown as number;
81+
}
82+
});
83+
84+
it('renders fallback UI when contributors are empty', async () => {
85+
const element = await ContributorsPage();
86+
render(element);
87+
88+
expect(screen.getByText(/No architects found/i)).toBeTruthy();
89+
expect(screen.getByText(/0 of 0 contributors/i)).toBeTruthy();
90+
expect(screen.getByRole('heading', { name: /THE COLLECTIVE/i })).toBeTruthy();
91+
expect(screen.getByText(/READY TO BUILD\?/i)).toBeTruthy();
92+
});
93+
94+
it('maintains page layout and empty markers for empty contributor input', async () => {
95+
const element = await ContributorsPage();
96+
render(element);
97+
98+
expect(screen.getByRole('heading', { name: /THE VANGUARD/i })).toBeTruthy();
99+
expect(screen.getByRole('heading', { name: /THE COLLECTIVE/i })).toBeTruthy();
100+
expect(screen.getByText(/Explore The Elite/i)).toBeTruthy();
101+
expect(screen.queryByText(/View Profile/i)).toBeNull();
102+
});
103+
104+
it('handles fetch failures gracefully and still renders fallback state', async () => {
105+
global.fetch = vi.fn(() => Promise.reject(new Error('Network failure'))) as any;
106+
const element = await ContributorsPage();
107+
render(element);
108+
109+
expect(screen.getByText(/No architects found/i)).toBeTruthy();
110+
expect(screen.getByText(/0 of 0 contributors/i)).toBeTruthy();
111+
});
112+
113+
it('handles non-ok API responses without breaking the page', async () => {
114+
global.fetch = vi.fn(() =>
115+
Promise.resolve({
116+
ok: false,
117+
status: 500,
118+
headers: { get: vi.fn(() => null) },
119+
json: async () => [],
120+
})
121+
) as any;
122+
123+
const element = await ContributorsPage();
124+
render(element);
125+
126+
expect(screen.getByText(/No architects found/i)).toBeTruthy();
127+
expect(screen.getByText(/0 of 0 contributors/i)).toBeTruthy();
128+
});
129+
130+
it('does not emit console errors when the fallback page renders', async () => {
131+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
132+
const element = await ContributorsPage();
133+
render(element);
134+
135+
expect(errorSpy).not.toHaveBeenCalled();
136+
});
137+
});

0 commit comments

Comments
 (0)