Skip to content

Commit b0c6a37

Browse files
authored
merge: Add Unit Tests for Heatmap Mouse Interactivity and Tooltips (#8266)
## Description Adds mouse interaction and tooltip behavior test coverage for `components/dashboard/Heatmap.tsx` through the new test file `components/dashboard/Heatmap.mouse-interactivity.test.tsx`. ## Changes * Added tests simulating mouse hover, pointer movement, and touch interactions on interactive heatmap cells. * Verified responsive tooltip rendering and positioning based on user interaction coordinates. * Tested click and touch event propagation to ensure interactions trigger the expected callbacks. * Confirmed interactive elements expose appropriate cursor styles during hover. * Validated tooltip dismissal and overlay cleanup on mouse leave events. ## Test Coverage Implemented 5 test cases covering: 1. Mouse enter and hover interactions on active heatmap cells. 2. Responsive tooltip rendering with correct positioning and visibility. 3. Click and touch gesture propagation triggering expected event handlers. 4. Pointer cursor styling applied to interactive heatmap elements. 5. Mouse leave behavior hiding temporary tooltip and overlay elements. ## Validation * All tests pass successfully with `vitest run`. * Mouse and touch interactions are fully mocked for deterministic execution. * No production code changes required. * Improves confidence that interactive heatmap behavior remains stable across future updates. ## Verification ✅ `vitest run` passes successfully. Fixes #2557 ## Pillar - [ ] 🎨 Pillar 1 — New Theme Design - [ ] 📐 Pillar 2 — Geometric SVG Improvement - [ ] 🕐 Pillar 3 — Timezone Logic Optimization - [x] 🛠️ Other (Bug fix, refactoring, docs) ## Visual Preview Can Check in the Files Changed Section. ## 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): ...`). - [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. - [x] 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 61ec3f9 + 86332ad commit b0c6a37

1 file changed

Lines changed: 144 additions & 0 deletions

File tree

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { render, screen, fireEvent } from '@testing-library/react';
2+
import userEvent from '@testing-library/user-event';
3+
import { describe, it, expect, vi, beforeEach } from 'vitest';
4+
import Heatmap from './Heatmap';
5+
import type { ActivityData } from '@/types/dashboard';
6+
7+
// Mock TranslationContext to return translation keys or formatted fallback strings
8+
vi.mock('@/context/TranslationContext', () => ({
9+
useTranslation: () => ({
10+
t: (key: string, options?: Record<string, string>) => {
11+
if (options?.count && options?.date) {
12+
return `${options.count} contributions on ${options.date}`;
13+
}
14+
return key;
15+
},
16+
}),
17+
}));
18+
19+
// Mock VisualizationTooltip component for precise inspection during tests
20+
vi.mock('./VisualizationTooltip', () => ({
21+
default: ({
22+
title,
23+
children,
24+
x,
25+
y,
26+
}: {
27+
title: string;
28+
children: React.ReactNode;
29+
x: number;
30+
y: number;
31+
}) => (
32+
<div role="tooltip" data-testid="visualization-tooltip" data-x={x} data-y={y}>
33+
<div>{title}</div>
34+
{children}
35+
</div>
36+
),
37+
}));
38+
39+
// Mock ResizeObserver which is used inside Heatmap.tsx
40+
global.ResizeObserver = class ResizeObserver {
41+
observe() {}
42+
unobserve() {}
43+
disconnect() {}
44+
};
45+
46+
describe('Heatmap Mouse Interactivity & Touch Event Propagation', () => {
47+
const mockData: ActivityData[] = [
48+
{ date: '2026-06-01', count: 5, intensity: 3 },
49+
{ date: '2026-06-02', count: 0, intensity: 0 },
50+
];
51+
52+
beforeEach(() => {
53+
vi.clearAllMocks();
54+
});
55+
56+
// Test 1: Tooltip display & computed coordinates on mouseenter / hover
57+
it('displays tooltip with computed coordinates on mouseenter over a grid cell', async () => {
58+
render(<Heatmap data={mockData} />);
59+
60+
const gridCell = screen.getAllByRole('gridcell')[0];
61+
62+
// Mock bounding rectangle for coordinate calculations
63+
vi.spyOn(gridCell, 'getBoundingClientRect').mockReturnValue({
64+
left: 100,
65+
top: 200,
66+
width: 14,
67+
height: 14,
68+
right: 114,
69+
bottom: 214,
70+
x: 100,
71+
y: 200,
72+
toJSON: () => {},
73+
});
74+
75+
await userEvent.hover(gridCell);
76+
77+
const tooltip = screen.getByTestId('visualization-tooltip');
78+
expect(tooltip).toBeInTheDocument();
79+
80+
// Check calculated tooltip coordinates (x = 100 + 14/2 = 107, y = 200 - 10 = 190)
81+
expect(tooltip).toHaveAttribute('data-x', '107');
82+
expect(tooltip).toHaveAttribute('data-y', '190');
83+
// formatTooltipDate formats '2026-06-01' -> 'Jun 1, 2026'
84+
expect(tooltip).toHaveTextContent('5 contributions on Jun 1, 2026');
85+
});
86+
87+
// Test 2: Hide tooltip on mouseleave
88+
it('hides the temporary tooltip visual overlay on mouseleave', async () => {
89+
render(<Heatmap data={mockData} />);
90+
91+
const gridCell = screen.getAllByRole('gridcell')[0];
92+
93+
await userEvent.hover(gridCell);
94+
expect(screen.getByTestId('visualization-tooltip')).toBeInTheDocument();
95+
96+
await userEvent.unhover(gridCell);
97+
expect(screen.queryByTestId('visualization-tooltip')).not.toBeInTheDocument();
98+
});
99+
100+
// Test 3: Cursor styles and interactive feedback classes
101+
it('applies interactive cursor classes (cursor-pointer) and focus styles to grid cells', () => {
102+
render(<Heatmap data={mockData} />);
103+
104+
const gridCell = screen.getAllByRole('gridcell')[0];
105+
106+
expect(gridCell).toHaveClass('cursor-pointer');
107+
expect(gridCell).toHaveClass('hover:scale-125');
108+
});
109+
110+
// Test 4: Focus and Blur interactivity (Keyboard/Touch Accessibility)
111+
it('shows and hides tooltip on focus and blur events for accessibility', () => {
112+
render(<Heatmap data={mockData} />);
113+
114+
const gridCell = screen.getAllByRole('gridcell')[0];
115+
116+
fireEvent.focus(gridCell);
117+
expect(screen.getByTestId('visualization-tooltip')).toBeInTheDocument();
118+
119+
fireEvent.blur(gridCell);
120+
expect(screen.queryByTestId('visualization-tooltip')).not.toBeInTheDocument();
121+
});
122+
123+
// Test 5: Touch gesture and event propagation on cell interaction
124+
it('handles touch/click gesture propagation without throwing errors', () => {
125+
const handleClick = vi.fn();
126+
127+
render(
128+
<div onClick={handleClick}>
129+
<Heatmap data={mockData} />
130+
</div>
131+
);
132+
133+
const gridCell = screen.getAllByRole('gridcell')[0];
134+
135+
// Trigger touchStart event
136+
fireEvent.touchStart(gridCell, {
137+
touches: [{ clientX: 105, clientY: 205 }],
138+
});
139+
140+
// Trigger click to check propagation to parent containers
141+
fireEvent.click(gridCell);
142+
expect(handleClick).toHaveBeenCalledTimes(1);
143+
});
144+
});

0 commit comments

Comments
 (0)