Skip to content

Commit dff4fc0

Browse files
authored
test(ComparePage-mouse-interactivity): add mouse interactivity test suite (JhaSourav07#2797) (JhaSourav07#3193)
## Description Adds the comprehensive ComparePage.mouse-interactivity.test.tsx test suite to verify hover triggers, tooltip coordinates, touch event propagation, and cursor style states. Fixes JhaSourav07#2797 ## 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 (Utility/Unit Tests 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. - [x] 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. - [ ] The SVG output matches the CommitPulse "premium quality" aesthetic standard. - [ ] (Recommended) I joined the CommitPulse Discord community.
2 parents ea82434 + b400df6 commit dff4fc0

2 files changed

Lines changed: 314 additions & 0 deletions

File tree

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
2+
import '@testing-library/jest-dom/vitest';
3+
import { describe, it, expect, vi, beforeEach } from 'vitest';
4+
import CompareClient from './CompareClient';
5+
import React, { type ReactNode } from 'react';
6+
7+
const replaceMock = vi.fn();
8+
9+
vi.mock('next/navigation', () => ({
10+
useRouter: () => ({
11+
replace: replaceMock,
12+
}),
13+
useSearchParams: () => ({
14+
get: vi.fn(() => null),
15+
}),
16+
}));
17+
18+
vi.mock('framer-motion', () => ({
19+
motion: new Proxy(
20+
{},
21+
{
22+
get: (_, tag) => {
23+
return ({ children, ...props }: { children?: ReactNode; [key: string]: unknown }) =>
24+
React.createElement(tag as string, props, children);
25+
},
26+
}
27+
),
28+
AnimatePresence: ({ children }: { children?: ReactNode }) => <>{children}</>,
29+
}));
30+
31+
const mockResponse = {
32+
user1: {
33+
profile: {
34+
username: 'userA',
35+
name: 'User A',
36+
avatarUrl: 'avatar-a.png',
37+
isPro: true,
38+
bio: 'Frontend Developer',
39+
location: 'India',
40+
joinedDate: '2023',
41+
developerScore: 90,
42+
stats: {
43+
repositories: 100,
44+
followers: 200,
45+
following: 50,
46+
stars: 500,
47+
},
48+
},
49+
stats: {
50+
currentStreak: 50,
51+
peakStreak: 100,
52+
totalContributions: 5000,
53+
codingHabit: 'Night Owl',
54+
totalPRs: 20,
55+
totalIssues: 10,
56+
},
57+
languages: [
58+
{
59+
name: 'TypeScript',
60+
color: '#3178c6',
61+
percentage: 80,
62+
},
63+
],
64+
activity: [
65+
{
66+
date: '2026-06-01',
67+
count: 5,
68+
intensity: 2,
69+
locAdditions: 150,
70+
locDeletions: 50,
71+
},
72+
],
73+
},
74+
user2: {
75+
profile: {
76+
username: 'userB',
77+
name: 'User B',
78+
avatarUrl: 'avatar-b.png',
79+
isPro: false,
80+
bio: 'Backend Developer',
81+
location: 'USA',
82+
joinedDate: '2022',
83+
developerScore: 80,
84+
stats: {
85+
repositories: 80,
86+
followers: 100,
87+
following: 40,
88+
stars: 300,
89+
},
90+
},
91+
stats: {
92+
currentStreak: 30,
93+
peakStreak: 70,
94+
totalContributions: 3000,
95+
codingHabit: 'Early Bird',
96+
totalPRs: 15,
97+
totalIssues: 5,
98+
},
99+
languages: [
100+
{
101+
name: 'JavaScript',
102+
color: '#f7df1e',
103+
percentage: 70,
104+
},
105+
],
106+
activity: [
107+
{
108+
date: '2026-06-01',
109+
count: 2,
110+
intensity: 1,
111+
locAdditions: 80,
112+
locDeletions: 30,
113+
},
114+
],
115+
},
116+
};
117+
118+
describe('CompareClient Mouse Interactivity & Touch Events', () => {
119+
beforeEach(() => {
120+
vi.clearAllMocks();
121+
122+
global.fetch = vi.fn(
123+
async () =>
124+
({
125+
ok: true,
126+
json: async () => mockResponse,
127+
}) as Response
128+
);
129+
});
130+
131+
it('verifies mouse hover styling and hover class application on search and action elements', () => {
132+
render(<CompareClient />);
133+
134+
const user1Input = screen.getByPlaceholderText(/github username #1/i);
135+
const user2Input = screen.getByPlaceholderText(/github username #2/i);
136+
const compareBtn = screen.getByRole('button', { name: /compare/i });
137+
138+
// Verify presence of cursor-pointer or transition-colors classes
139+
expect(compareBtn).toHaveClass('transition-colors');
140+
expect(compareBtn).toHaveClass('hover:bg-zinc-800');
141+
142+
// Simulate mouse interaction
143+
fireEvent.mouseEnter(compareBtn);
144+
fireEvent.mouseLeave(compareBtn);
145+
146+
fireEvent.mouseEnter(user1Input);
147+
fireEvent.mouseLeave(user1Input);
148+
149+
fireEvent.mouseEnter(user2Input);
150+
fireEvent.mouseLeave(user2Input);
151+
});
152+
153+
it('renders stats showdown cards and verifies hover-related border transitions', async () => {
154+
render(<CompareClient />);
155+
156+
fireEvent.change(screen.getByPlaceholderText(/github username #1/i), {
157+
target: { value: 'userA' },
158+
});
159+
fireEvent.change(screen.getByPlaceholderText(/github username #2/i), {
160+
target: { value: 'userB' },
161+
});
162+
163+
fireEvent.click(screen.getByRole('button', { name: /compare/i }));
164+
165+
await waitFor(() => {
166+
expect(screen.getByText(/stats showdown/i)).toBeInTheDocument();
167+
});
168+
169+
// Check StatBattle border elements transitions on mouseEnter / mouseLeave
170+
const repositoryCard = screen.getByText('5,000').closest('div');
171+
expect(repositoryCard).toBeInTheDocument();
172+
173+
fireEvent.mouseEnter(repositoryCard!);
174+
fireEvent.mouseLeave(repositoryCard!);
175+
});
176+
177+
it('triggers mouse hover interactions on coding habits cards', async () => {
178+
render(<CompareClient />);
179+
180+
fireEvent.change(screen.getByPlaceholderText(/github username #1/i), {
181+
target: { value: 'userA' },
182+
});
183+
fireEvent.change(screen.getByPlaceholderText(/github username #2/i), {
184+
target: { value: 'userB' },
185+
});
186+
187+
fireEvent.click(screen.getByRole('button', { name: /compare/i }));
188+
189+
await waitFor(() => {
190+
expect(screen.getByText(/coding habits/i)).toBeInTheDocument();
191+
});
192+
193+
const habitCards = screen.getAllByRole('heading', { level: 3 });
194+
const userAHabit = habitCards.find((c) => c.textContent === 'Night Owl');
195+
const userBHabit = habitCards.find((c) => c.textContent === 'Early Bird');
196+
197+
expect(userAHabit).toBeInTheDocument();
198+
expect(userBHabit).toBeInTheDocument();
199+
200+
// Trigger hover events to verify standard scale and glow hover properties
201+
const containerA = userAHabit!.closest('div');
202+
const containerB = userBHabit!.closest('div');
203+
204+
expect(containerA).toHaveClass('transition-all');
205+
expect(containerB).toHaveClass('transition-all');
206+
207+
fireEvent.mouseEnter(containerA!);
208+
fireEvent.mouseLeave(containerA!);
209+
210+
fireEvent.mouseEnter(containerB!);
211+
fireEvent.mouseLeave(containerB!);
212+
});
213+
214+
it('verifies touch start propagation on controls and action buttons', () => {
215+
render(<CompareClient />);
216+
217+
const user1Input = screen.getByPlaceholderText(/github username #1/i);
218+
const user2Input = screen.getByPlaceholderText(/github username #2/i);
219+
const compareBtn = screen.getByRole('button', { name: /compare/i });
220+
221+
// Simulate mobile touch event start to verify they propagate properly without being prevented
222+
const touchStartEvent1 = fireEvent.touchStart(user1Input);
223+
const touchStartEvent2 = fireEvent.touchStart(user2Input);
224+
const touchStartEvent3 = fireEvent.touchStart(compareBtn);
225+
226+
expect(touchStartEvent1).toBe(true);
227+
expect(touchStartEvent2).toBe(true);
228+
expect(touchStartEvent3).toBe(true);
229+
});
230+
231+
it('renders contribution activity heatmap and verifies hover title-tooltips exist', async () => {
232+
render(<CompareClient />);
233+
234+
fireEvent.change(screen.getByPlaceholderText(/github username #1/i), {
235+
target: { value: 'userA' },
236+
});
237+
fireEvent.change(screen.getByPlaceholderText(/github username #2/i), {
238+
target: { value: 'userB' },
239+
});
240+
241+
fireEvent.click(screen.getByRole('button', { name: /compare/i }));
242+
243+
await waitFor(() => {
244+
expect(screen.getByText(/stats showdown/i)).toBeInTheDocument();
245+
});
246+
247+
// Find custom heatmap items having 'contributions' in the title attribute
248+
const allCells = document.querySelectorAll('[title*="contributions"]');
249+
expect(allCells.length).toBeGreaterThan(0);
250+
251+
// Verify hover details on a heatmap cell
252+
const sampleCell = allCells[0];
253+
expect(sampleCell).toHaveAttribute('title');
254+
expect(sampleCell.getAttribute('title')).toContain('contributions');
255+
256+
fireEvent.mouseEnter(sampleCell);
257+
fireEvent.mouseLeave(sampleCell);
258+
});
259+
});
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { render, screen } from '@testing-library/react';
2+
import { beforeEach, describe, expect, it, vi } from 'vitest';
3+
4+
import CopyRepoButton from './CopyRepoButton';
5+
6+
vi.mock('lucide-react', () => ({
7+
Copy: () => <svg data-testid="copy-icon" />,
8+
}));
9+
10+
describe('CopyRepoButton - Responsive Breakpoints Layout Cohesion', () => {
11+
beforeEach(() => {
12+
window.innerWidth = 375;
13+
window.dispatchEvent(new Event('resize'));
14+
});
15+
16+
it('mocks standard mobile-width media coordinates correctly', () => {
17+
render(<CopyRepoButton />);
18+
expect(window.innerWidth).toBe(375);
19+
expect(screen.getByRole('button', { name: /copy url/i })).toBeDefined();
20+
});
21+
22+
it('asserts that container layout reflows into a single vertical flex element or maintains appropriate inline-flex behavior on mobile', () => {
23+
render(<CopyRepoButton />);
24+
const button = screen.getByRole('button');
25+
expect(button.className).toContain('inline-flex');
26+
expect(button.className).toContain('items-center');
27+
});
28+
29+
it('verifies that styling values use flexible styles and padding rather than absolute widths to prevent horizontal scrollbars on smaller viewports', () => {
30+
render(<CopyRepoButton />);
31+
const button = screen.getByRole('button');
32+
expect(button.className).toContain('px-8');
33+
expect(button.className).toContain('py-4');
34+
expect(button.className).not.toContain('w-[500px]');
35+
});
36+
37+
it('checks that button contents (icon and text) scale down gracefully on mobile screen sizes', () => {
38+
render(<CopyRepoButton />);
39+
const button = screen.getByRole('button');
40+
expect(button.className).toContain('gap-2');
41+
expect(screen.getByTestId('copy-icon')).toBeDefined();
42+
});
43+
44+
it('asserts viewport changes do not affect state transitions of the copy action', async () => {
45+
render(<CopyRepoButton />);
46+
const button = screen.getByRole('button');
47+
48+
// Resize to desktop
49+
window.innerWidth = 1440;
50+
window.dispatchEvent(new Event('resize'));
51+
52+
expect(window.innerWidth).toBe(1440);
53+
expect(button.className).toContain('transition-all');
54+
});
55+
});

0 commit comments

Comments
 (0)