Skip to content

Commit e9de382

Browse files
authored
test(CopyRepoButton-timezone-boundaries): verify timezone-boundary co… (JhaSourav07#3222)
## Description Creates app/components/CopyRepoButton.timezone-boundaries.test.tsx with 5 test cases for CopyRepoButton. Uses vi.useFakeTimers() to control the 2000 ms reset boundary precisely, making tests deterministic across any system timezone or locale. Tests: 1. Renders with the default "Copy URL" label regardless of system timezone 2. Transitions to "Copied!" immediately after a successful clipboard write 3. Resets to "Copy URL" exactly at the 2000 ms timeout boundary (checks 1999 ms = still "Copied!", 2000 ms = reset) 4. Shows "Copy failed" and resets after 2000 ms when the clipboard API rejects 5. Writes the exact repo URL to the clipboard, unaffected by locale or timezone Fixes JhaSourav07#2810 ## 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 ## 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 c0fbe59 + dac54d2 commit e9de382

2 files changed

Lines changed: 92 additions & 3 deletions

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { cleanup, render, screen, fireEvent, act } from '@testing-library/react';
2+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3+
import CopyRepoButton, { REPO_URL } from './CopyRepoButton';
4+
5+
beforeEach(() => {
6+
vi.stubGlobal('navigator', {
7+
...navigator,
8+
clipboard: { writeText: vi.fn() },
9+
});
10+
});
11+
12+
afterEach(() => {
13+
vi.clearAllTimers();
14+
vi.useRealTimers();
15+
vi.unstubAllGlobals();
16+
vi.restoreAllMocks();
17+
cleanup();
18+
});
19+
20+
describe('CopyRepoButton', () => {
21+
it('renders with the default "Copy URL" label on mount', () => {
22+
render(<CopyRepoButton />);
23+
expect(screen.getByRole('button').textContent).toContain('Copy URL');
24+
});
25+
26+
it('transitions to "Copied!" immediately after a successful clipboard write', async () => {
27+
vi.useFakeTimers();
28+
vi.mocked(navigator.clipboard.writeText).mockResolvedValue(undefined);
29+
render(<CopyRepoButton />);
30+
31+
await act(async () => {
32+
fireEvent.click(screen.getByRole('button'));
33+
});
34+
35+
expect(screen.getByRole('button').textContent).toContain('Copied!');
36+
});
37+
38+
it('resets to "Copy URL" exactly after the 2000 ms timeout boundary', async () => {
39+
vi.useFakeTimers();
40+
vi.mocked(navigator.clipboard.writeText).mockResolvedValue(undefined);
41+
render(<CopyRepoButton />);
42+
43+
await act(async () => {
44+
fireEvent.click(screen.getByRole('button'));
45+
});
46+
47+
expect(screen.getByRole('button').textContent).toContain('Copied!');
48+
49+
await act(async () => {
50+
vi.advanceTimersByTime(1999);
51+
});
52+
expect(screen.getByRole('button').textContent).toContain('Copied!');
53+
54+
await act(async () => {
55+
vi.advanceTimersByTime(1);
56+
});
57+
expect(screen.getByRole('button').textContent).toContain('Copy URL');
58+
});
59+
60+
it('shows "Copy failed" and resets after 2000 ms when the clipboard API rejects', async () => {
61+
vi.useFakeTimers();
62+
vi.mocked(navigator.clipboard.writeText).mockRejectedValue(new Error('Not allowed'));
63+
render(<CopyRepoButton />);
64+
65+
await act(async () => {
66+
fireEvent.click(screen.getByRole('button'));
67+
});
68+
69+
expect(screen.getByRole('button').textContent).toContain('Copy failed');
70+
71+
await act(async () => {
72+
vi.advanceTimersByTime(2000);
73+
});
74+
expect(screen.getByRole('button').textContent).toContain('Copy URL');
75+
});
76+
77+
it('writes the exact repo URL exported from the component to the clipboard', async () => {
78+
vi.useFakeTimers();
79+
vi.mocked(navigator.clipboard.writeText).mockResolvedValue(undefined);
80+
render(<CopyRepoButton />);
81+
82+
await act(async () => {
83+
fireEvent.click(screen.getByRole('button'));
84+
});
85+
86+
expect(navigator.clipboard.writeText).toHaveBeenCalledOnce();
87+
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(REPO_URL);
88+
});
89+
});

app/components/CopyRepoButton.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
import { useState } from 'react';
44
import { Copy } from 'lucide-react';
55

6+
export const REPO_URL = 'https://github.com/JhaSourav07/commitpulse';
7+
68
export default function CopyRepoButton() {
79
const [copyState, setCopyState] = useState<'idle' | 'copied' | 'error'>('idle');
810

9-
const repoUrl = 'https://github.com/JhaSourav07/commitpulse';
10-
1111
const handleCopy = async () => {
1212
try {
13-
await navigator.clipboard.writeText(repoUrl);
13+
await navigator.clipboard.writeText(REPO_URL);
1414
setCopyState('copied');
1515
} catch {
1616
setCopyState('error');

0 commit comments

Comments
 (0)