Skip to content

Commit 86332ad

Browse files
committed
test(Heatmap): add unit tests for mouse interactivity, tooltips, and touch events
1 parent ded12c8 commit 86332ad

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)