Vollständige Next.js 15 Portfolio-Website mit: - Blog-System mit 100+ Artikeln - Supabase-Integration - Responsive Design mit Tailwind CSS - TypeScript-Konfiguration - Testing-Setup mit Vitest und Playwright Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
102 lines
3.1 KiB
TypeScript
102 lines
3.1 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { render, screen } from '@testing-library/react';
|
|
import Skills from '../Skills';
|
|
|
|
// Mock next-intl
|
|
vi.mock('next-intl', () => ({
|
|
useTranslations: () => {
|
|
const t = (key: string) => {
|
|
const translations: Record<string, any> = {
|
|
title: 'Skills',
|
|
'skills': [
|
|
{ name: 'Python', level: 90, description: 'Backend development' },
|
|
{ name: 'TypeScript', level: 85, description: 'Frontend development' },
|
|
],
|
|
};
|
|
return translations[key] || key;
|
|
};
|
|
t.raw = (key: string) => {
|
|
if (key === 'skills') {
|
|
return [
|
|
{ name: 'Python', level: 90, description: 'Backend development' },
|
|
{ name: 'TypeScript', level: 85, description: 'Frontend development' },
|
|
];
|
|
}
|
|
return [];
|
|
};
|
|
return t;
|
|
},
|
|
}));
|
|
|
|
// Mock framer-motion
|
|
vi.mock('framer-motion', () => ({
|
|
motion: {
|
|
div: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
|
},
|
|
AnimatePresence: ({ children }: any) => <>{children}</>,
|
|
}));
|
|
|
|
describe('Skills Component', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('renders without crashing', () => {
|
|
const { container } = render(<Skills />);
|
|
expect(container).toBeTruthy();
|
|
});
|
|
|
|
it('displays the skills title', () => {
|
|
render(<Skills />);
|
|
expect(screen.getByText('Skills')).toBeTruthy();
|
|
});
|
|
|
|
it('renders skill items with correct data', () => {
|
|
render(<Skills />);
|
|
expect(screen.getByText('Python')).toBeTruthy();
|
|
expect(screen.getByText('TypeScript')).toBeTruthy();
|
|
expect(screen.getByText('90%')).toBeTruthy();
|
|
expect(screen.getByText('85%')).toBeTruthy();
|
|
});
|
|
|
|
it('renders progress bars with correct attributes', () => {
|
|
const { container } = render(<Skills />);
|
|
const progressBars = container.querySelectorAll('[role="progressbar"]');
|
|
expect(progressBars.length).toBeGreaterThan(0);
|
|
|
|
// Check first progress bar has correct attributes
|
|
const firstProgressBar = progressBars[0];
|
|
expect(firstProgressBar.getAttribute('aria-valuenow')).toBe('90');
|
|
expect(firstProgressBar.getAttribute('aria-valuemin')).toBe('0');
|
|
expect(firstProgressBar.getAttribute('aria-valuemax')).toBe('100');
|
|
});
|
|
|
|
it('has memoized event handlers', () => {
|
|
// This test verifies that useCallback is used by checking
|
|
// that the component uses the pattern (we can't directly test memoization in unit tests)
|
|
const { rerender } = render(<Skills />);
|
|
|
|
// Re-render the component
|
|
rerender(<Skills />);
|
|
|
|
// If the component re-renders without errors, memoization is likely working
|
|
expect(screen.getByText('Skills')).toBeTruthy();
|
|
});
|
|
|
|
it('handles missing skills gracefully', () => {
|
|
// Re-mock with empty skills
|
|
vi.mock('next-intl', () => ({
|
|
useTranslations: () => {
|
|
const t = (key: string) => key;
|
|
t.raw = () => {
|
|
throw new Error('No skills');
|
|
};
|
|
return t;
|
|
},
|
|
}));
|
|
|
|
const { container } = render(<Skills />);
|
|
expect(container).toBeTruthy();
|
|
});
|
|
});
|