Skip to main content

testing-frontend

Use when writing component tests, testing user interactions, mocking APIs, or setting up Vitest/React Testing Library/Vue Test Utils for frontend applications.

معلومات المصدر

المستودع
MadAppGang/claude-code
آخر نشاط في المصدر
٢٠ يناير ٢٠٢٦ في ٠٨:١٦
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٢٨٤
التفرعات
٢٥

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
testing-frontend
version
1.0.0
description
Use when writing component tests, testing user interactions, mocking APIs, or setting up Vitest/React Testing Library/Vue Test Utils for frontend applications.
keywords
["frontend testing","Vitest","React Testing Library","Vue Test Utils","component testing","user event testing","mocking","accessibility testing"]
plugin
dev
updated
2026-01-20T00:00:00.000Z
# Frontend Testing Patterns ## Overview Testing patterns for frontend applications using Vitest and React Testing Library / Vue Test Utils. ## Testing Philosophy ### User-Centric Testing Test behavior, not implementation. Query elements the way users would find them. ```tsx // BAD: Testing implementation expect(wrapper.state('isOpen')).toBe(true); expect(wrapper.find('.modal-class').exists()).toBe(true); // GOOD: Testing behavior expect(screen.getByRole('dialog')).toBeInTheDocument(); expect(screen.getByText('Modal Title')).toBeVisible(); ``` ### Query Priority Use queries in this order (most to least preferred): 1. `getByRole` - Accessible to everyone 2. `getByLabelText` - Form elements 3. `getByPlaceholderText` - Inputs 4. `getByText` - Non-interactive elements 5. `getByDisplayValue` - Form current values 6. `getByAltText` - Images 7. `getByTestId` - Last resort ## Component Testing (React) ### Basic Component Test ```tsx import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { UserCard } from './UserCard'; describe('UserCard', () => { const user = { id: '1', name: 'John Doe', email: 'john@example.com' }; it('renders user information', () => { render(<UserCard user={user} />); expect(screen.getByText('John Doe')).toBeInTheDocument(); expect(screen.getByText('john@example.com')).toBeInTheDocument(); }); it('calls onSelect when clicked', async () => { const onSelect = vi.fn(); const userEvt = userEvent.setup(); render(<UserCard user={user} onSelect={onSelect} />); await userEvt.click(screen.getByRole('button')); expect(onSelect).toHaveBeenCalledWith(user); }); }); ``` ### Testing Async Components ```tsx import { render, screen, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { UserList } from './UserList'; // Mock API vi.mock('@/api', () => ({ getUsers: vi.fn(), })); describe('UserList', () => { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } }, }); const wrapper = ({ children }) => ( <QueryClientProvider client={queryClient}> {children} </QueryClientProvider> ); beforeEach(() => { queryClient.clear(); }); it('shows loading state', () => { api.getUsers.mockImplementation(() => new Promise(() => {})); render(<UserList />, { wrapper }); expect(screen.getByText('Loading...')).toBeInTheDocument(); }); it('shows users when loaded', async () => { api.getUsers.mockResolvedValue([ { id: '1', name: 'John' }, { id: '2', name: 'Jane' }, ]); render(<UserList />, { wrapper }); await waitFor(() => { expect(screen.getByText('John')).toBeInTheDocument(); expect(screen.getByText('Jane')).toBeInTheDocument(); }); }); it('shows error message on failure', async () => { api.getUsers.mockRejectedValue(new Error('Network error')); render(<UserList />, { wrapper }); await waitFor(() => { expect(screen.getByText(/error/i)).toBeInTheDocument(); }); }); }); ``` ### Testing Forms ```tsx import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { LoginForm } from './LoginForm'; describe('LoginForm', () => { it('submits form with valid data', async () => { const onSubmit = vi.fn(); const user = userEvent.setup(); render(<LoginForm onSubmit={onSubmit} />); await user.type(screen.getByLabelText(/email/i), 'test@example.com'); await user.type(screen.getByLabelText(/password/i), 'password123'); await user.click(screen.getByRole('button', { name: /submit/i })); expect(onSubmit).toHaveBeenCalledWith({ email: 'test@example.com', password: 'password123', }); }); it('shows validation errors', async () => { const user = userEvent.setup(); render(<LoginForm onSubmit={vi.fn()} />); // Submit empty form await user.click(screen.getByRole('button', { name: /submit/i })); expect(screen.getByText(/email is required/i)).toBeInTheDocument(); expect(screen.getByText(/password is required/i)).toBeInTheDocument(); }); it('disables submit button while submitting', async () => { const user = userEvent.setup(); render( <LoginForm onSubmit={() => new Promise((resolve) => setTimeout(resolve, 100))} /> ); await user.type(screen.getByLabelText(/email/i), 'test@example.com'); await user.type(screen.getByLabelText(/password/i), 'password123'); await user.click(screen.getByRole('button', { name: /submit/i })); expect(screen.getByRole('button', { name: /submitting/i })).toBeDisabled(); }); }); ``` ## Component Testing (Vue) ### Basic Component Test ```ts import { mount } from '@vue/test-utils'; import UserCard from './UserCard.vue'; describe('UserCard', () => { const user = { id: '1', name: 'John Doe', email: 'john@example.com' }; it('renders user information', () => { const wrapper = mount(UserCard, { props: { user }, }); expect(wrapper.text()).toContain('John Doe'); expect(wrapper.text()).toContain('john@example.com'); }); it('emits select event when clicked', async () => { const wrapper = mount(UserCard, { props: { user }, }); await wrapper.find('button').trigger('click'); expect(wrapper.emitted('select')).toBeTruthy(); expect(wrapper.emitted('select')[0]).toEqual([user]); }); }); ``` ### Testing with Pinia ```ts import { mount } from '@vue/test-utils'; import { createTestingPinia } from '@pinia/testing'; import UserList from './UserList.vue'; import { useUserStore } from '@/stores/userStore'; describe('UserList', () => { it('renders users from store', () => { const wrapper = mount(UserList, { global: { plugins: [ createTestingPinia({ initialState: { user: { users: [ { id: '1', name: 'John' }, { id: '2', name: 'Jane' }, ], }, }, }), ], }, }); expect(wrapper.text()).toContain('John'); expect(wrapper.text()).toContain('Jane'); }); it('calls fetchUsers on mount', () => { mount(UserList, { global: { plugins: [createTestingPinia()], }, }); const store = useUserStore(); expect(store.fetchUsers).toHaveBeenCalled(); }); }); ``` ## Hook Testing ```tsx import { renderHook, act, waitFor } from '@testing-library/react'; import { useCounter } from './useCounter'; describe('useCounter', () => { it('initializes with default value', () => { const { result } = renderHook(() => useCounter()); expect(result.current.count).toBe(0); }); it('initializes with custom value', () => { const { result } = renderHook(() => useCounter({ initial: 10 })); expect(result.current.count).toBe(10); }); it('increments counter', () => { const { result } = renderHook(() => useCounter()); act(() => { result.current.increment(); }); expect(result.current.count).toBe(1); }); it('respects max limit', () => { const { result } = renderHook(() => useCounter({ initial: 9, max: 10 })); act(() => { result.current.increment(); result.current.increment(); }); expect(result.current.count).toBe(10); }); }); ``` ## Mocking ### API Mocking ```tsx // __mocks__/api.ts export const api = { getUsers: vi.fn(), createUser: vi.fn(), updateUser: vi.fn(), deleteUser: vi.fn(), }; // In test vi.mock('@/api'); beforeEach(() => { vi.clearAllMocks(); }); it('fetches users', async () => { api.getUsers.mockResolvedValue([{ id: '1', name: 'John' }]); render(<UserList />); await waitFor(() => { expect(api.getUsers).toHaveBeenCalledTimes(1); }); }); ``` ### Module Mocking ```tsx // Mock entire module vi.mock('@/utils/date', () => ({ formatDate: vi.fn(() => '2024-01-01'), })); // Mock specific export vi.mock('@/config', async () => { const actual = await vi.importActual('@/config'); return { ...actual, API_URL: 'http://test-api.com', }; }); ``` ### Timer Mocking ```tsx describe('Debounced search', () => { beforeEach(() => { vi.useFakeTimers(); });
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub