jest.mock('../../../../src/renderer/components/ui/Typography', () => ({
__esModule: true,
default: ({ children, variant, color, className, onClick }: any) => (
{children}
),
}));
jest.mock('../../../../src/renderer/components/ui/Button', () => ({
Button: ({ children, variant, onClick, className }: any) => (
),
}));
jest.mock('react-icons/fi', () => ({
FiExternalLink: () => ,
FiCheck: () => ,
FiCopy: () => ,
FiEye: () => ,
FiEyeOff: () => ,
}));
const mockClipboard = {
writeText: jest.fn().mockResolvedValue(undefined),
};
Object.defineProperty(navigator, 'clipboard', {
value: mockClipboard,
writable: true,
configurable: true,
});
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ApiKeyGuide } from '../../../../src/renderer/components/ui/ApiKeyGuide';
describe('ApiKeyGuide Component', () => {
const defaultProps = {
provider: 'openai' as const,
onClose: jest.fn(),
};
beforeEach(() => {
jest.clearAllMocks();
});
describe('Component Structure', () => {
it('renders with correct title for OpenAI provider', () => {
render();
expect(screen.getByText('🔑')).toBeInTheDocument();
expect(screen.getByText('How to Get Your OpenAI API Key')).toBeInTheDocument();
});
it('renders with correct title for Anthropic provider', () => {
render();
expect(screen.getByText('🔑')).toBeInTheDocument();
expect(screen.getByText('How to Get Your Anthropic API Key')).toBeInTheDocument();
});
it('renders close button when onClose prop is provided', () => {
render();
expect(screen.getByTestId('button-outline')).toBeInTheDocument();
expect(screen.getByText('Close Guide')).toBeInTheDocument();
});
it('does not render close button when onClose prop is not provided', () => {
render();
expect(screen.queryByTestId('button-outline')).not.toBeInTheDocument();
expect(screen.queryByText('Close Guide')).not.toBeInTheDocument();
});
it('renders introductory text', () => {
render();
expect(screen.getByText('Follow these step-by-step instructions to get your API key. Don\'t worry - it\'s easier than it looks!')).toBeInTheDocument();
});
});
describe('OpenAI Guide Content', () => {
it('renders all OpenAI steps', () => {
render();
expect(screen.getByText('Create an OpenAI Account')).toBeInTheDocument();
expect(screen.getByText('Add Billing Information')).toBeInTheDocument();
expect(screen.getByText('Generate Your API Key')).toBeInTheDocument();
expect(screen.getByText('Copy Your API Key')).toBeInTheDocument();
});
it('renders OpenAI step numbers correctly', () => {
render();
const stepCircles = document.querySelectorAll('.w-8.h-8.rounded-full');
expect(stepCircles).toHaveLength(5); // Including the "Paste the Key Below" step
});
it('renders OpenAI external links', () => {
render();
expect(screen.getByText('Sign up for OpenAI')).toBeInTheDocument();
expect(screen.getByText('Go to Billing Settings')).toBeInTheDocument();
expect(screen.getByText('Open API Keys Page')).toBeInTheDocument();
const externalLinks = screen.getAllByTestId('external-link-icon');
expect(externalLinks.length).toBeGreaterThan(0);
});
it('renders OpenAI help section', () => {
render();
expect(screen.getByText('Need Help?')).toBeInTheDocument();
expect(screen.getByText(/If you run into any issues/)).toBeInTheDocument();
expect(screen.getByText(/OpenAI documentation/)).toBeInTheDocument();
});
});
describe('Anthropic Guide Content', () => {
it('renders all Anthropic steps', () => {
render();
expect(screen.getByText('Create an Anthropic Account')).toBeInTheDocument();
expect(screen.getByText('Add Credits to Your Account')).toBeInTheDocument();
expect(screen.getByText('Generate Your API Key')).toBeInTheDocument();
expect(screen.getByText('Copy Your API Key')).toBeInTheDocument();
});
it('renders Anthropic step numbers correctly', () => {
render();
const stepElements = screen.getAllByText(/[1-4]/);
expect(stepElements).toHaveLength(4);
});
it('renders Anthropic external links', () => {
render();
expect(screen.getByText('Sign up for Anthropic')).toBeInTheDocument();
expect(screen.getByText('Add Credits')).toBeInTheDocument();
expect(screen.getByText('Open API Keys Page')).toBeInTheDocument();
const externalLinks = screen.getAllByTestId('external-link-icon');
expect(externalLinks.length).toBeGreaterThan(0);
});
it('renders Anthropic help section', () => {
render();
expect(screen.getByText('Need Help?')).toBeInTheDocument();
expect(screen.getByText(/If you run into any issues/)).toBeInTheDocument();
expect(screen.getByText(/Anthropic documentation/)).toBeInTheDocument();
});
});
describe('Step Component', () => {
it('renders step with correct number when not completed', () => {
render();
const firstSteps = screen.getAllByText('1');
expect(firstSteps.length).toBeGreaterThan(0);
});
it('renders step with check icon when completed', () => {
render();
expect(screen.getByText('Create an OpenAI Account')).toBeInTheDocument();
});
it('renders step content correctly', () => {
render();
expect(screen.getByText('First, you\'ll need to create an account with OpenAI if you don\'t already have one.')).toBeInTheDocument();
});
});
describe('API Key Display and Copy Functionality', () => {
it('renders API key display area', () => {
render();
expect(screen.getByText('Your API key will look like this:')).toBeInTheDocument();
});
it('renders copy button', () => {
render();
expect(screen.getByTestId('copy-icon')).toBeInTheDocument();
});
it('calls clipboard API when copy button is clicked', async () => {
const user = userEvent.setup();
render();
const copyButton = screen.getByTestId('copy-icon').closest('button');
if (copyButton) {
await user.click(copyButton);
expect(mockClipboard.writeText).toHaveBeenCalledWith('sk-your-api-key-here');
}
});
it('renders show/hide toggle for API key', () => {
render();
const hasEyeIcon = screen.queryByTestId('eye-icon');
const hasEyeOffIcon = screen.queryByTestId('eye-off-icon');
expect(hasEyeIcon || hasEyeOffIcon).toBeTruthy();
});
it('toggles API key visibility', async () => {
const user = userEvent.setup();
render();
const toggleButton = screen.getByTestId('eye-icon').closest('button') ||
screen.getByTestId('eye-off-icon').closest('button');
if (toggleButton) {
await user.click(toggleButton);
}
});
});
describe('User Interactions', () => {
it('calls onClose when close button is clicked', async () => {
const user = userEvent.setup();
render();
const closeButton = screen.getByText('Close Guide');
await user.click(closeButton);
expect(defaultProps.onClose).toHaveBeenCalledTimes(1);
});
it('opens external links in new tabs', () => {
render();
const links = screen.getAllByRole('link');
links.forEach(link => {
expect(link).toHaveAttribute('target', '_blank');
expect(link).toHaveAttribute('rel', 'noopener noreferrer');
});
});
});
describe('Styling and Accessibility', () => {
it('applies correct CSS classes', () => {
const { container } = render();
const mainContainer = container.firstChild as HTMLElement;
expect(mainContainer).toHaveClass('max-w-4xl', 'mx-auto');
const stepElements = container.querySelectorAll('.flex.space-x-4');
expect(stepElements.length).toBeGreaterThan(0);
});
it('renders with proper ARIA attributes', () => {
render();
const headings = screen.getAllByRole('heading');
expect(headings.length).toBeGreaterThan(0);
});
it('renders tip sections with proper styling', () => {
render();
const tipSections = document.querySelectorAll('.bg-blue-50');
expect(tipSections.length).toBeGreaterThan(0);
});
});
describe('Error Handling and Edge Cases', () => {
it('handles invalid provider gracefully', () => {
expect(() => {
render();
}).not.toThrow();
});
it('renders without crashing when props are missing', () => {
expect(() => {
render();
}).not.toThrow();
});
it('handles clipboard API errors gracefully', async () => {
mockClipboard.writeText.mockRejectedValueOnce(new Error('Clipboard not available'));
const user = userEvent.setup();
render();
const copyButton = screen.getByTestId('copy-icon').closest('button');
if (copyButton) {
await user.click(copyButton);
expect(mockClipboard.writeText).toHaveBeenCalled();
}
});
});
describe('Content Accuracy', () => {
it('displays correct OpenAI URLs', () => {
render();
expect(screen.getByText('Sign up for OpenAI').closest('a')).toHaveAttribute('href', 'https://platform.openai.com/signup');
expect(screen.getByText('Go to Billing Settings').closest('a')).toHaveAttribute('href', 'https://platform.openai.com/account/billing');
expect(screen.getAllByText('Open API Keys Page')[0].closest('a')).toHaveAttribute('href', 'https://platform.openai.com/api-keys');
});
it('displays correct Anthropic URLs', () => {
render();
expect(screen.getByText('Sign up for Anthropic').closest('a')).toHaveAttribute('href', 'https://console.anthropic.com/account');
expect(screen.getByText('Add Credits').closest('a')).toHaveAttribute('href', 'https://console.anthropic.com/account/billing');
expect(screen.getAllByText('Open API Keys Page')[0].closest('a')).toHaveAttribute('href', 'https://console.anthropic.com/settings/keys');
});
it('displays appropriate billing information for each provider', () => {
render();
expect(screen.getByText(/only pay for what you use/)).toBeInTheDocument();
expect(screen.getByText(/\$5 in free credits/)).toBeInTheDocument();
render();
expect(screen.getByText(/prepaid credit system/)).toBeInTheDocument();
expect(screen.getByText(/Claude 3.5 Haiku/)).toBeInTheDocument();
});
});
});