Skip to content

Commit 4ed3984

Browse files
Merge branch 'main' into fix/wrapped-duplicate-contributions-fetch
2 parents bb0f94a + b130694 commit 4ed3984

5 files changed

Lines changed: 186 additions & 10 deletions

app/components/CopyRepoButton.massive-scaling.test.tsx

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1-
import { render, screen } from '@testing-library/react';
2-
import { describe, it, expect, vi } from 'vitest';
1+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
2+
import { beforeEach, describe, it, expect, vi } from 'vitest';
33
import CopyRepoButton from './CopyRepoButton';
44

55
vi.mock('lucide-react', () => ({
66
Copy: () => <svg data-testid="copy-icon" />,
77
}));
88

9+
beforeEach(() => {
10+
vi.clearAllMocks();
11+
Object.assign(navigator, {
12+
clipboard: {
13+
writeText: vi.fn().mockResolvedValue(undefined),
14+
},
15+
});
16+
});
17+
918
describe('CopyRepoButton Massive Scaling', () => {
1019
it('renders 100 buttons without crashing', () => {
1120
render(
@@ -63,4 +72,35 @@ describe('CopyRepoButton Massive Scaling', () => {
6372
}
6473
}).not.toThrow();
6574
});
75+
76+
it('shows copied state after a successful clipboard write', async () => {
77+
render(<CopyRepoButton />);
78+
79+
fireEvent.click(screen.getByRole('button', { name: /copy url/i }));
80+
81+
await waitFor(() => {
82+
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
83+
'https://github.com/JhaSourav07/commitpulse'
84+
);
85+
});
86+
87+
expect(screen.getByText('Copied!')).toBeDefined();
88+
});
89+
90+
it('shows an error state when clipboard write fails', async () => {
91+
vi.mocked(navigator.clipboard.writeText).mockRejectedValueOnce(new Error('Permission denied'));
92+
93+
render(<CopyRepoButton />);
94+
95+
fireEvent.click(screen.getByRole('button', { name: /copy url/i }));
96+
97+
await waitFor(() => {
98+
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
99+
'https://github.com/JhaSourav07/commitpulse'
100+
);
101+
});
102+
103+
expect(screen.queryByText('Copied!')).toBeNull();
104+
expect(screen.getByText('Copy failed')).toBeDefined();
105+
});
66106
});

app/components/CopyRepoButton.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@ import { useState } from 'react';
44
import { Copy } from 'lucide-react';
55

66
export default function CopyRepoButton() {
7-
const [copied, setCopied] = useState(false);
7+
const [copyState, setCopyState] = useState<'idle' | 'copied' | 'error'>('idle');
88

99
const repoUrl = 'https://github.com/JhaSourav07/commitpulse';
1010

1111
const handleCopy = async () => {
12-
await navigator.clipboard.writeText(repoUrl);
12+
try {
13+
await navigator.clipboard.writeText(repoUrl);
14+
setCopyState('copied');
15+
} catch {
16+
setCopyState('error');
17+
}
1318

14-
setCopied(true);
15-
16-
setTimeout(() => {
17-
setCopied(false);
18-
}, 2000);
19+
setTimeout(() => setCopyState('idle'), 2000);
1920
};
2021

2122
return (
@@ -24,7 +25,7 @@ export default function CopyRepoButton() {
2425
className="inline-flex items-center gap-2 rounded-2xl border border-black/10 bg-white/60 px-8 py-4 font-semibold transition-all duration-300 hover:scale-105 dark:border-white/10 dark:bg-white/5"
2526
>
2627
<Copy className="h-5 w-5" />
27-
{copied ? 'Copied!' : 'Copy URL'}
28+
{copyState === 'copied' ? 'Copied!' : copyState === 'error' ? 'Copy failed' : 'Copy URL'}
2829
</button>
2930
);
3031
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { TrackUserProtection } from './track-user-protection';
3+
4+
describe('TrackUserProtection Accessibility Compliance', () => {
5+
it('validates correct use of aria roles and labels on indicator markup', () => {
6+
const tracker = TrackUserProtection.getInstance();
7+
const element = { role: 'status', 'aria-live': 'polite' };
8+
expect(tracker).toBeDefined();
9+
expect(element.role).toBe('status');
10+
expect(element['aria-live']).toBe('polite');
11+
});
12+
13+
it('asserts elements accepting focus maintain visible outline styles', () => {
14+
const focusableElement = { focusable: true, style: { outline: '2px solid purple' } };
15+
expect(focusableElement.focusable).toBe(true);
16+
expect(focusableElement.style.outline).toContain('solid');
17+
});
18+
19+
it('verifies tooltip elements announce correct accessibility descriptions', () => {
20+
const tooltip = { 'aria-describedby': 'tooltip-desc', textContent: 'User tracking status' };
21+
expect(tooltip['aria-describedby']).toBe('tooltip-desc');
22+
expect(tooltip.textContent).toBe('User tracking status');
23+
});
24+
25+
it('tests keyboard control paths to ensure correct tab index order', () => {
26+
const items = [
27+
{ id: 'btn-1', tabIndex: 0 },
28+
{ id: 'btn-2', tabIndex: 0 },
29+
{ id: 'btn-3', tabIndex: -1 },
30+
];
31+
const activeTabs = items.filter((item) => item.tabIndex >= 0);
32+
expect(activeTabs.length).toBe(2);
33+
});
34+
35+
it('confirms logical hierarchy ordering of headings', () => {
36+
const headings = ['H1', 'H2', 'H3'];
37+
const isOrdered = headings.every((h, idx) => idx === 0 || headings[idx - 1] < h);
38+
expect(isOrdered).toBe(true);
39+
});
40+
});
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { describe, it, expect, vi } from 'vitest';
2+
import { TrackUserProtection } from './track-user-protection';
3+
4+
describe('TrackUserProtection Mouse Interactivity', () => {
5+
// Test 1: Trigger simulated mouseenter/hover gestures on active segments
6+
it('handles simulated hover state flags without throwing', () => {
7+
const tracker = TrackUserProtection.getInstance();
8+
const hoverState = { active: false, coordinates: { x: 0, y: 0 } };
9+
10+
// Simulate hover
11+
hoverState.active = true;
12+
hoverState.coordinates = { x: 120, y: 340 };
13+
14+
expect(tracker).toBeDefined();
15+
expect(hoverState.active).toBe(true);
16+
expect(hoverState.coordinates.x).toBe(120);
17+
});
18+
19+
// Test 2: Verify that responsive tooltip layouts display at computed coordinates
20+
it('computes responsive tooltip offset coordinates correctly', () => {
21+
const computeOffset = (x: number, y: number) => ({ top: y - 10, left: x + 15 });
22+
const offset = computeOffset(100, 200);
23+
expect(offset.top).toBe(190);
24+
expect(offset.left).toBe(115);
25+
});
26+
27+
// Test 3: Test custom click/touch gestures and ensure click events propagate correctly
28+
it('verifies click/touch propagation events do not trigger double calls', () => {
29+
const onClick = vi.fn();
30+
const simulateTouch = (e: { stopPropagation: () => void }) => {
31+
e.stopPropagation();
32+
onClick();
33+
};
34+
const mockEvent = { stopPropagation: vi.fn() };
35+
simulateTouch(mockEvent);
36+
expect(onClick).toHaveBeenCalledTimes(1);
37+
expect(mockEvent.stopPropagation).toHaveBeenCalledTimes(1);
38+
});
39+
40+
// Test 4: Assert appropriate cursor style classes (like pointer) are applied on hover
41+
it('checks if correct cursor pointer class is returned for interactive elements', () => {
42+
const getCursorStyle = (isHovered: boolean) =>
43+
isHovered ? 'cursor-pointer' : 'cursor-default';
44+
expect(getCursorStyle(true)).toBe('cursor-pointer');
45+
expect(getCursorStyle(false)).toBe('cursor-default');
46+
});
47+
48+
// Test 5: Check that mouseleave events successfully hide temporary overlay visuals
49+
it('hides active tooltip overlay when mouseleave is triggered', () => {
50+
const overlay = { visible: true };
51+
const onMouseLeave = () => {
52+
overlay.visible = false;
53+
};
54+
onMouseLeave();
55+
expect(overlay.visible).toBe(false);
56+
});
57+
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { TrackUserProtection } from './track-user-protection';
3+
4+
describe('TrackUserProtection Theme Contrast and Visual Cohesion', () => {
5+
it('emulates dual theme configuration presets correctly', () => {
6+
const tracker = TrackUserProtection.getInstance();
7+
const themes = {
8+
dark: { bg: '#0b0f19', text: '#ffffff' },
9+
light: { bg: '#ffffff', text: '#0b0f19' },
10+
};
11+
expect(tracker).toBeDefined();
12+
expect(themes.dark.bg).toBe('#0b0f19');
13+
expect(themes.light.bg).toBe('#ffffff');
14+
});
15+
16+
it('asserts styling adapts properly according to current theme preset', () => {
17+
const getBgColor = (theme: 'dark' | 'light') => (theme === 'dark' ? '#0f172a' : '#f8fafc');
18+
expect(getBgColor('dark')).toBe('#0f172a');
19+
expect(getBgColor('light')).toBe('#f8fafc');
20+
});
21+
22+
it('verifies contrast ratio compliance thresholds are met', () => {
23+
// WCAG AAA requires 7:1 for normal text, AA requires 4.5:1
24+
const contrastRatio = 7.2;
25+
expect(contrastRatio).toBeGreaterThanOrEqual(4.5);
26+
});
27+
28+
it('checks presence of active tailwind or class properties', () => {
29+
const cardClasses = ['dark:bg-slate-900', 'bg-white', 'text-slate-100'];
30+
expect(cardClasses).toContain('dark:bg-slate-900');
31+
});
32+
33+
it('ensures background colors do not obstruct foreground content elements', () => {
34+
const container = { bgOpacity: 0.8, textVisible: true };
35+
expect(container.bgOpacity).toBeLessThan(1);
36+
expect(container.textVisible).toBe(true);
37+
});
38+
});

0 commit comments

Comments
 (0)