Skip to content

Commit e9ce89e

Browse files
author
kali
committed
fix(frontend): handle copy repo clipboard failures
1 parent 9f6451a commit e9ce89e

2 files changed

Lines changed: 51 additions & 10 deletions

File tree

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
}

0 commit comments

Comments
 (0)