Add comprehensive unit tests for homepage components and utilities

- Introduced unit tests for homepage components: `QuickStartCode`, `Header`, `DemoCredentialsModal`, `AnimatedTerminal`, `CTASection`, and `StatsSection`.
- Added utility tests for `chart-colors` including opacity, palettes, and gradient validation.
- Mocked dependencies (`framer-motion`, `react-syntax-highlighter`, `DemoCredentialsModal`) for isolated testing.
- Verified accessibility features, animations, and interactive behaviors across components.
This commit is contained in:
2025-11-08 17:06:14 +01:00
parent fe289228e1
commit b630559e0b
14 changed files with 1603 additions and 37 deletions

View File

@@ -0,0 +1,99 @@
/**
* Tests for AnimatedTerminal component
*/
import { render, screen } from '@testing-library/react';
import { AnimatedTerminal } from '@/components/home/AnimatedTerminal';
// Mock framer-motion
jest.mock('framer-motion', () => ({
motion: {
div: ({ children, ...props }: any) => <div {...props}>{children}</div>,
},
}));
// Mock Next.js Link
jest.mock('next/link', () => ({
__esModule: true,
default: ({ children, href, ...props }: any) => {
return (
<a href={href} {...props}>
{children}
</a>
);
},
}));
// IntersectionObserver is already mocked in jest.setup.js
describe('AnimatedTerminal', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
it('renders the section heading', () => {
render(<AnimatedTerminal />);
expect(screen.getByText('Get Started in Seconds')).toBeInTheDocument();
expect(screen.getByText(/Clone, run, and start building/i)).toBeInTheDocument();
});
it('renders terminal window with header', () => {
render(<AnimatedTerminal />);
expect(screen.getByText('bash')).toBeInTheDocument();
});
it('renders Try Live Demo button', () => {
render(<AnimatedTerminal />);
const demoLink = screen.getByRole('link', { name: /try live demo/i });
expect(demoLink).toHaveAttribute('href', '/login');
});
it('displays message about trying demo', () => {
render(<AnimatedTerminal />);
expect(screen.getByText(/Or try the live demo without installing/i)).toBeInTheDocument();
});
it('starts animation when component mounts', () => {
render(<AnimatedTerminal />);
// Animation should start because IntersectionObserver mock triggers immediately
// Advance timers to show first command
jest.advanceTimersByTime(1000);
// Check if animated content appears (the mock renders all commands immediately in tests)
const terminalContent = screen.getByText('bash').parentElement?.parentElement;
expect(terminalContent).toBeInTheDocument();
});
it('renders terminal with proper structure', () => {
render(<AnimatedTerminal />);
// Verify terminal window has proper structure
const bashIndicator = screen.getByText('bash');
expect(bashIndicator).toBeInTheDocument();
expect(bashIndicator.parentElement).toBeInTheDocument();
});
describe('Accessibility', () => {
it('has descriptive text for screen readers', () => {
render(<AnimatedTerminal />);
expect(screen.getByText('Get Started in Seconds')).toBeInTheDocument();
});
it('has proper link to demo', () => {
render(<AnimatedTerminal />);
const demoLink = screen.getByRole('link', { name: /try live demo/i });
expect(demoLink).toBeInTheDocument();
});
});
});

View File

@@ -0,0 +1,132 @@
/**
* Tests for CTASection component
*/
import { render, screen, fireEvent } from '@testing-library/react';
import { CTASection } from '@/components/home/CTASection';
// Mock framer-motion
jest.mock('framer-motion', () => ({
motion: {
div: ({ children, ...props }: any) => <div {...props}>{children}</div>,
},
}));
// Mock Next.js Link
jest.mock('next/link', () => ({
__esModule: true,
default: ({ children, href, ...props }: any) => {
return (
<a href={href} {...props}>
{children}
</a>
);
},
}));
// Mock DemoCredentialsModal
jest.mock('@/components/home/DemoCredentialsModal', () => ({
DemoCredentialsModal: ({ open, onClose }: any) => (
open ? <div data-testid="demo-modal">
<button onClick={onClose}>Close Modal</button>
</div> : null
),
}));
describe('CTASection', () => {
it('renders main headline', () => {
render(<CTASection />);
expect(screen.getByText(/Start Building,/i)).toBeInTheDocument();
expect(screen.getByText(/Not Boilerplating/i)).toBeInTheDocument();
});
it('renders subtext with key messaging', () => {
render(<CTASection />);
expect(screen.getByText(/Clone the repository, read the docs/i)).toBeInTheDocument();
expect(screen.getByText(/Free forever, MIT licensed/i)).toBeInTheDocument();
});
it('renders GitHub CTA button', () => {
render(<CTASection />);
const githubLink = screen.getByRole('link', { name: /get started on github/i });
expect(githubLink).toHaveAttribute('href', 'https://github.com/your-org/fast-next-template');
expect(githubLink).toHaveAttribute('target', '_blank');
expect(githubLink).toHaveAttribute('rel', 'noopener noreferrer');
});
it('renders Try Live Demo button', () => {
render(<CTASection />);
const demoButton = screen.getByRole('button', { name: /try live demo/i });
expect(demoButton).toBeInTheDocument();
});
it('renders Read Documentation link', () => {
render(<CTASection />);
const docsLink = screen.getByRole('link', { name: /read documentation/i });
expect(docsLink).toHaveAttribute('href', 'https://github.com/your-org/fast-next-template#documentation');
expect(docsLink).toHaveAttribute('target', '_blank');
expect(docsLink).toHaveAttribute('rel', 'noopener noreferrer');
});
it('renders help text with internal links', () => {
render(<CTASection />);
expect(screen.getByText(/Need help getting started\?/i)).toBeInTheDocument();
const componentShowcaseLink = screen.getByRole('link', { name: /component showcase/i });
expect(componentShowcaseLink).toHaveAttribute('href', '/dev');
const adminDashboardLink = screen.getByRole('link', { name: /admin dashboard demo/i });
expect(adminDashboardLink).toHaveAttribute('href', '/admin');
});
it('opens demo modal when Try Live Demo button is clicked', () => {
render(<CTASection />);
const demoButton = screen.getByRole('button', { name: /try live demo/i });
fireEvent.click(demoButton);
expect(screen.getByTestId('demo-modal')).toBeInTheDocument();
});
it('closes demo modal when close is called', () => {
render(<CTASection />);
// Open modal
const demoButton = screen.getByRole('button', { name: /try live demo/i });
fireEvent.click(demoButton);
expect(screen.getByTestId('demo-modal')).toBeInTheDocument();
// Close modal
const closeButton = screen.getByText('Close Modal');
fireEvent.click(closeButton);
expect(screen.queryByTestId('demo-modal')).not.toBeInTheDocument();
});
describe('Accessibility', () => {
it('has proper external link attributes', () => {
render(<CTASection />);
const externalLinks = [
screen.getByRole('link', { name: /get started on github/i }),
screen.getByRole('link', { name: /read documentation/i }),
];
externalLinks.forEach(link => {
expect(link).toHaveAttribute('target', '_blank');
expect(link).toHaveAttribute('rel', 'noopener noreferrer');
});
});
it('has descriptive button text', () => {
render(<CTASection />);
expect(screen.getByRole('button', { name: /try live demo/i })).toBeInTheDocument();
});
});
});

View File

@@ -0,0 +1,170 @@
/**
* Tests for DemoCredentialsModal component
*/
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { DemoCredentialsModal } from '@/components/home/DemoCredentialsModal';
// Mock Next.js Link
jest.mock('next/link', () => ({
__esModule: true,
default: ({ children, href, ...props }: any) => {
return (
<a href={href} {...props}>
{children}
</a>
);
},
}));
describe('DemoCredentialsModal', () => {
const mockOnClose = jest.fn();
beforeEach(() => {
mockOnClose.mockClear();
// Mock clipboard API
Object.assign(navigator, {
clipboard: {
writeText: jest.fn(() => Promise.resolve()),
},
});
});
it('renders when open is true', () => {
render(<DemoCredentialsModal open={true} onClose={mockOnClose} />);
expect(screen.getByText('Try the Live Demo')).toBeInTheDocument();
expect(screen.getByText(/Use these credentials to explore/i)).toBeInTheDocument();
});
it('does not render when open is false', () => {
render(<DemoCredentialsModal open={false} onClose={mockOnClose} />);
expect(screen.queryByText('Try the Live Demo')).not.toBeInTheDocument();
});
it('displays regular user credentials', () => {
render(<DemoCredentialsModal open={true} onClose={mockOnClose} />);
expect(screen.getByText('Regular User')).toBeInTheDocument();
expect(screen.getByText('demo@example.com')).toBeInTheDocument();
expect(screen.getByText('Demo123!')).toBeInTheDocument();
expect(screen.getByText(/Access settings, organizations/i)).toBeInTheDocument();
});
it('displays admin user credentials', () => {
render(<DemoCredentialsModal open={true} onClose={mockOnClose} />);
expect(screen.getByText('Admin User (Superuser)')).toBeInTheDocument();
expect(screen.getByText('admin@example.com')).toBeInTheDocument();
expect(screen.getByText('Admin123!')).toBeInTheDocument();
expect(screen.getByText(/Full admin panel access/i)).toBeInTheDocument();
});
it('copies regular user credentials to clipboard', async () => {
render(<DemoCredentialsModal open={true} onClose={mockOnClose} />);
const copyButtons = screen.getAllByRole('button');
const regularCopyButton = copyButtons.find(btn => btn.textContent?.includes('Copy'));
fireEvent.click(regularCopyButton!);
await waitFor(() => {
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('demo@example.com\nDemo123!');
const copiedButtons = screen.getAllByRole('button');
const copiedButton = copiedButtons.find(btn => btn.textContent?.includes('Copied!'));
expect(copiedButton).toBeInTheDocument();
});
});
it('copies admin user credentials to clipboard', async () => {
render(<DemoCredentialsModal open={true} onClose={mockOnClose} />);
const copyButtons = screen.getAllByRole('button');
const adminCopyButton = copyButtons.filter(btn => btn.textContent?.includes('Copy'))[1];
fireEvent.click(adminCopyButton!);
await waitFor(() => {
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('admin@example.com\nAdmin123!');
const copiedButtons = screen.getAllByRole('button');
const copiedButton = copiedButtons.find(btn => btn.textContent?.includes('Copied!'));
expect(copiedButton).toBeInTheDocument();
});
});
it('resets copied state after 2 seconds', async () => {
jest.useFakeTimers();
render(<DemoCredentialsModal open={true} onClose={mockOnClose} />);
const copyButtons = screen.getAllByRole('button');
const copyButton = copyButtons.find(btn => btn.textContent?.includes('Copy'));
fireEvent.click(copyButton!);
await waitFor(() => {
const copiedButtons = screen.getAllByRole('button');
const copiedButton = copiedButtons.find(btn => btn.textContent?.includes('Copied!'));
expect(copiedButton).toBeInTheDocument();
});
jest.advanceTimersByTime(2000);
await waitFor(() => {
const buttons = screen.getAllByRole('button');
const copiedButton = buttons.find(btn => btn.textContent?.includes('Copied!'));
expect(copiedButton).toBeUndefined();
});
jest.useRealTimers();
});
it('handles clipboard copy failure gracefully', async () => {
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
Object.assign(navigator, {
clipboard: {
writeText: jest.fn(() => Promise.reject(new Error('Clipboard error'))),
},
});
render(<DemoCredentialsModal open={true} onClose={mockOnClose} />);
const copyButtons = screen.getAllByRole('button');
const copyButton = copyButtons.find(btn => btn.textContent?.includes('Copy'));
fireEvent.click(copyButton!);
await waitFor(() => {
expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to copy:', expect.any(Error));
});
consoleErrorSpy.mockRestore();
});
it('calls onClose when close button is clicked', () => {
render(<DemoCredentialsModal open={true} onClose={mockOnClose} />);
// Find the "Close" button (filter to get the one that's visible and is the footer button)
const closeButtons = screen.getAllByRole('button', { name: 'Close' });
const footerCloseButton = closeButtons.find(btn =>
btn.textContent === 'Close' && !btn.querySelector('.sr-only')
);
fireEvent.click(footerCloseButton!);
expect(mockOnClose).toHaveBeenCalled();
});
it('has a link to login page', () => {
render(<DemoCredentialsModal open={true} onClose={mockOnClose} />);
const loginLink = screen.getByRole('link', { name: /go to login/i });
expect(loginLink).toHaveAttribute('href', '/login');
});
it('calls onClose when login link is clicked', () => {
render(<DemoCredentialsModal open={true} onClose={mockOnClose} />);
const loginLink = screen.getByRole('link', { name: /go to login/i });
fireEvent.click(loginLink);
expect(mockOnClose).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,152 @@
/**
* Tests for Header component
*/
import { render, screen, fireEvent } from '@testing-library/react';
import { Header } from '@/components/home/Header';
// Mock Next.js Link
jest.mock('next/link', () => ({
__esModule: true,
default: ({ children, href, ...props }: any) => {
return (
<a href={href} {...props}>
{children}
</a>
);
},
}));
// Mock DemoCredentialsModal
jest.mock('@/components/home/DemoCredentialsModal', () => ({
DemoCredentialsModal: ({ open, onClose }: any) => (
open ? <div data-testid="demo-modal">
<button onClick={onClose}>Close Modal</button>
</div> : null
),
}));
describe('Header', () => {
it('renders logo', () => {
render(<Header />);
expect(screen.getByText('FastNext')).toBeInTheDocument();
expect(screen.getByText('Template')).toBeInTheDocument();
});
it('logo links to homepage', () => {
render(<Header />);
const logoLink = screen.getByRole('link', { name: /fastnext template/i });
expect(logoLink).toHaveAttribute('href', '/');
});
describe('Desktop Navigation', () => {
it('renders navigation links', () => {
render(<Header />);
expect(screen.getByRole('link', { name: 'Components' })).toHaveAttribute('href', '/dev');
expect(screen.getByRole('link', { name: 'Admin Demo' })).toHaveAttribute('href', '/admin');
});
it('renders GitHub link with star badge', () => {
render(<Header />);
const githubLinks = screen.getAllByRole('link', { name: /github/i });
const desktopGithubLink = githubLinks.find(link =>
link.getAttribute('href')?.includes('github.com')
);
expect(desktopGithubLink).toHaveAttribute('href', 'https://github.com/your-org/fast-next-template');
expect(desktopGithubLink).toHaveAttribute('target', '_blank');
expect(desktopGithubLink).toHaveAttribute('rel', 'noopener noreferrer');
});
it('renders Try Demo button', () => {
render(<Header />);
const demoButton = screen.getByRole('button', { name: /try demo/i });
expect(demoButton).toBeInTheDocument();
});
it('renders Login button', () => {
render(<Header />);
const loginLinks = screen.getAllByRole('link', { name: /login/i });
expect(loginLinks.length).toBeGreaterThan(0);
expect(loginLinks[0]).toHaveAttribute('href', '/login');
});
it('opens demo modal when Try Demo button is clicked', () => {
render(<Header />);
const demoButton = screen.getByRole('button', { name: /try demo/i });
fireEvent.click(demoButton);
expect(screen.getByTestId('demo-modal')).toBeInTheDocument();
});
});
describe('Mobile Menu', () => {
it('renders mobile menu toggle button', () => {
render(<Header />);
// SheetTrigger wraps the button, so we need to find it by aria-label
const menuButton = screen.getByRole('button', { name: /toggle menu/i });
expect(menuButton).toBeInTheDocument();
});
it('mobile menu contains navigation links', () => {
render(<Header />);
// Note: SheetContent is hidden by default in tests, but we can verify the links exist
// The actual mobile menu behavior is tested in E2E tests
const componentsLinks = screen.getAllByRole('link', { name: /components/i });
expect(componentsLinks.length).toBeGreaterThan(0);
});
it('mobile menu contains GitHub link', () => {
render(<Header />);
const githubLinks = screen.getAllByRole('link', { name: /github/i });
expect(githubLinks.length).toBeGreaterThan(0);
});
});
describe('Demo Modal Integration', () => {
it('closes demo modal when close is called', () => {
render(<Header />);
// Open modal
const demoButton = screen.getByRole('button', { name: /try demo/i });
fireEvent.click(demoButton);
expect(screen.getByTestId('demo-modal')).toBeInTheDocument();
// Close modal
const closeButton = screen.getByText('Close Modal');
fireEvent.click(closeButton);
expect(screen.queryByTestId('demo-modal')).not.toBeInTheDocument();
});
});
describe('Accessibility', () => {
it('has proper ARIA labels for icon buttons', () => {
render(<Header />);
const menuButton = screen.getByRole('button', { name: /toggle menu/i });
expect(menuButton).toHaveAccessibleName();
});
it('has proper external link attributes', () => {
render(<Header />);
const githubLinks = screen.getAllByRole('link', { name: /github/i });
const externalLink = githubLinks.find(link =>
link.getAttribute('href')?.includes('github.com')
);
expect(externalLink).toHaveAttribute('target', '_blank');
expect(externalLink).toHaveAttribute('rel', 'noopener noreferrer');
});
});
});

View File

@@ -0,0 +1,137 @@
/**
* Tests for HeroSection component
*/
import { render, screen, fireEvent } from '@testing-library/react';
import { HeroSection } from '@/components/home/HeroSection';
// Mock framer-motion
jest.mock('framer-motion', () => ({
motion: {
div: ({ children, ...props }: any) => <div {...props}>{children}</div>,
h1: ({ children, ...props }: any) => <h1 {...props}>{children}</h1>,
p: ({ children, ...props }: any) => <p {...props}>{children}</p>,
},
}));
// Mock Next.js Link
jest.mock('next/link', () => ({
__esModule: true,
default: ({ children, href, ...props }: any) => {
return (
<a href={href} {...props}>
{children}
</a>
);
},
}));
// Mock DemoCredentialsModal
jest.mock('@/components/home/DemoCredentialsModal', () => ({
DemoCredentialsModal: ({ open, onClose }: any) => (
open ? <div data-testid="demo-modal">
<button onClick={onClose}>Close Modal</button>
</div> : null
),
}));
describe('HeroSection', () => {
it('renders badge with key highlights', () => {
render(<HeroSection />);
expect(screen.getByText('MIT Licensed')).toBeInTheDocument();
expect(screen.getAllByText('97% Test Coverage')[0]).toBeInTheDocument();
expect(screen.getByText('Production Ready')).toBeInTheDocument();
});
it('renders main headline', () => {
render(<HeroSection />);
expect(screen.getAllByText(/Everything You Need to Build/i)[0]).toBeInTheDocument();
expect(screen.getAllByText(/Modern Web Applications/i)[0]).toBeInTheDocument();
});
it('renders subheadline with key messaging', () => {
render(<HeroSection />);
expect(screen.getByText(/Production-ready FastAPI \+ Next.js template/i)).toBeInTheDocument();
expect(screen.getByText(/Start building features on day one/i)).toBeInTheDocument();
});
it('renders Try Live Demo button', () => {
render(<HeroSection />);
const demoButton = screen.getByRole('button', { name: /try live demo/i });
expect(demoButton).toBeInTheDocument();
});
it('renders View on GitHub link', () => {
render(<HeroSection />);
const githubLink = screen.getByRole('link', { name: /view on github/i });
expect(githubLink).toHaveAttribute('href', 'https://github.com/your-org/fast-next-template');
expect(githubLink).toHaveAttribute('target', '_blank');
expect(githubLink).toHaveAttribute('rel', 'noopener noreferrer');
});
it('renders Explore Components link', () => {
render(<HeroSection />);
const componentsLink = screen.getByRole('link', { name: /explore components/i });
expect(componentsLink).toHaveAttribute('href', '/dev');
});
it('displays test coverage stats', () => {
render(<HeroSection />);
const coverageTexts = screen.getAllByText('97%');
expect(coverageTexts.length).toBeGreaterThan(0);
const testCountTexts = screen.getAllByText('743');
expect(testCountTexts.length).toBeGreaterThan(0);
expect(screen.getAllByText(/Passing Tests/i)[0]).toBeInTheDocument();
expect(screen.getByText('0')).toBeInTheDocument();
expect(screen.getByText(/Flaky Tests/i)).toBeInTheDocument();
});
it('opens demo modal when Try Live Demo button is clicked', () => {
render(<HeroSection />);
const demoButton = screen.getByRole('button', { name: /try live demo/i });
fireEvent.click(demoButton);
expect(screen.getByTestId('demo-modal')).toBeInTheDocument();
});
it('closes demo modal when close is called', () => {
render(<HeroSection />);
// Open modal
const demoButton = screen.getByRole('button', { name: /try live demo/i });
fireEvent.click(demoButton);
expect(screen.getByTestId('demo-modal')).toBeInTheDocument();
// Close modal
const closeButton = screen.getByText('Close Modal');
fireEvent.click(closeButton);
expect(screen.queryByTestId('demo-modal')).not.toBeInTheDocument();
});
describe('Accessibility', () => {
it('has proper heading hierarchy', () => {
render(<HeroSection />);
const heading = screen.getAllByRole('heading', { level: 1 })[0];
expect(heading).toBeInTheDocument();
});
it('has proper external link attributes', () => {
render(<HeroSection />);
const githubLink = screen.getByRole('link', { name: /view on github/i });
expect(githubLink).toHaveAttribute('target', '_blank');
expect(githubLink).toHaveAttribute('rel', 'noopener noreferrer');
});
});
});

View File

@@ -0,0 +1,128 @@
/**
* Tests for QuickStartCode component
*/
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { QuickStartCode } from '@/components/home/QuickStartCode';
// Mock framer-motion
jest.mock('framer-motion', () => ({
motion: {
div: ({ children, ...props }: any) => <div {...props}>{children}</div>,
},
}));
// Mock react-syntax-highlighter
jest.mock('react-syntax-highlighter', () => ({
Prism: ({ children, ...props }: any) => <pre {...props}>{children}</pre>,
}));
jest.mock('react-syntax-highlighter/dist/esm/styles/prism', () => ({
vscDarkPlus: {},
}));
describe('QuickStartCode', () => {
beforeEach(() => {
// Mock clipboard API
Object.assign(navigator, {
clipboard: {
writeText: jest.fn(() => Promise.resolve()),
},
});
});
it('renders the section heading', () => {
render(<QuickStartCode />);
expect(screen.getByText('5-Minute Setup')).toBeInTheDocument();
expect(screen.getByText(/Clone, run, and start building/i)).toBeInTheDocument();
});
it('renders bash indicator', () => {
render(<QuickStartCode />);
expect(screen.getByText('bash')).toBeInTheDocument();
});
it('renders copy button', () => {
render(<QuickStartCode />);
const copyButton = screen.getByRole('button', { name: /copy/i });
expect(copyButton).toBeInTheDocument();
});
it('displays the code snippet', () => {
render(<QuickStartCode />);
const codeBlock = screen.getByText(/git clone/i);
expect(codeBlock).toBeInTheDocument();
});
it('copies code to clipboard when copy button is clicked', async () => {
render(<QuickStartCode />);
const copyButton = screen.getByRole('button', { name: /copy/i });
fireEvent.click(copyButton);
await waitFor(() => {
expect(navigator.clipboard.writeText).toHaveBeenCalled();
});
const clipboardContent = (navigator.clipboard.writeText as jest.Mock).mock.calls[0][0];
expect(clipboardContent).toContain('git clone');
expect(clipboardContent).toContain('docker-compose up');
expect(clipboardContent).toContain('pip install -r requirements.txt');
});
it('shows "Copied!" message after copying', async () => {
render(<QuickStartCode />);
const copyButton = screen.getByRole('button', { name: /copy/i });
fireEvent.click(copyButton);
await waitFor(() => {
expect(screen.getByText('Copied!')).toBeInTheDocument();
});
});
it('resets copied state after 2 seconds', async () => {
jest.useFakeTimers();
render(<QuickStartCode />);
const copyButton = screen.getByRole('button', { name: /copy/i });
fireEvent.click(copyButton);
await waitFor(() => {
expect(screen.getByText('Copied!')).toBeInTheDocument();
});
jest.advanceTimersByTime(2000);
await waitFor(() => {
expect(screen.queryByText('Copied!')).not.toBeInTheDocument();
expect(screen.getByText('Copy')).toBeInTheDocument();
});
jest.useRealTimers();
});
it('handles clipboard copy failure gracefully', async () => {
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
Object.assign(navigator, {
clipboard: {
writeText: jest.fn(() => Promise.reject(new Error('Clipboard error'))),
},
});
render(<QuickStartCode />);
const copyButton = screen.getByRole('button', { name: /copy/i });
fireEvent.click(copyButton);
await waitFor(() => {
expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to copy:', expect.any(Error));
});
consoleErrorSpy.mockRestore();
});
});

View File

@@ -0,0 +1,115 @@
/**
* Tests for StatsSection component
*/
import { render, screen } from '@testing-library/react';
import { StatsSection } from '@/components/home/StatsSection';
// Mock framer-motion
jest.mock('framer-motion', () => {
const React = require('react');
return {
motion: {
div: ({ children, ...props }: any) => <div {...props}>{children}</div>,
},
useInView: () => true, // Always in view for tests
};
});
describe('StatsSection', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
it('renders section heading', () => {
render(<StatsSection />);
expect(screen.getByText('Built with Quality in Mind')).toBeInTheDocument();
expect(screen.getByText(/Not just another template/i)).toBeInTheDocument();
});
it('renders all stat cards', () => {
render(<StatsSection />);
expect(screen.getByText('Test Coverage')).toBeInTheDocument();
expect(screen.getByText('Passing Tests')).toBeInTheDocument();
expect(screen.getByText('Flaky Tests')).toBeInTheDocument();
expect(screen.getByText('API Endpoints')).toBeInTheDocument();
});
it('displays stat descriptions', () => {
render(<StatsSection />);
expect(screen.getByText(/Comprehensive testing across backend and frontend/i)).toBeInTheDocument();
expect(screen.getByText(/Backend, frontend unit, and E2E tests/i)).toBeInTheDocument();
expect(screen.getByText(/Production-stable test suite/i)).toBeInTheDocument();
expect(screen.getByText(/Fully documented with OpenAPI/i)).toBeInTheDocument();
});
it('renders animated counters with correct suffixes', () => {
render(<StatsSection />);
// Counters start at 0, so we should see 0 initially
const counters = screen.getAllByText(/^[0-9]+[%+]?$/);
expect(counters.length).toBeGreaterThan(0);
});
it('animates counters when in view', () => {
render(<StatsSection />);
// The useInView mock returns true, so animation should start
// Advance timers to let the counter animation run
jest.advanceTimersByTime(2000);
// After animation, we should see the final values
// The component should eventually show the stat values
const statsSection = screen.getByText('Test Coverage').parentElement;
expect(statsSection).toBeInTheDocument();
});
it('displays icons for each stat', () => {
render(<StatsSection />);
// Icons are rendered via lucide-react components
// We can verify the stat cards are rendered with proper structure
const testCoverageCard = screen.getByText('Test Coverage').closest('div');
expect(testCoverageCard).toBeInTheDocument();
const passingTestsCard = screen.getByText('Passing Tests').closest('div');
expect(passingTestsCard).toBeInTheDocument();
const flakyTestsCard = screen.getByText('Flaky Tests').closest('div');
expect(flakyTestsCard).toBeInTheDocument();
const apiEndpointsCard = screen.getByText('API Endpoints').closest('div');
expect(apiEndpointsCard).toBeInTheDocument();
});
describe('Accessibility', () => {
it('has proper heading hierarchy', () => {
render(<StatsSection />);
const heading = screen.getByRole('heading', { name: /built with quality in mind/i });
expect(heading).toBeInTheDocument();
});
it('has descriptive labels for stats', () => {
render(<StatsSection />);
const statLabels = [
'Test Coverage',
'Passing Tests',
'Flaky Tests',
'API Endpoints',
];
statLabels.forEach(label => {
expect(screen.getByText(label)).toBeInTheDocument();
});
});
});
});