Component Testing
Vitest + RTLTest React components in isolation with React Testing Library.
Overview
Component tests verify that your React components render correctly and respond to user interactions as expected. Oorty uses Vitest with React Testing Library for a user-centric testing approach.
Test components the way users interact with them, not implementation details.
RTL queries encourage accessible markup and proper ARIA usage.
Full DOM rendering with jsdom for realistic component behavior.
Writing Component Tests
Place component tests in __tests__/components/ directory.
Testing a Button Component
// components/submit-button.tsx'use client'import { Button } from '@/components/ui/button'import { Loader2 } from 'lucide-react'interface SubmitButtonProps { children: React.ReactNode isLoading?: boolean disabled?: boolean onClick?: () => void}export function SubmitButton({ children, isLoading = false, disabled = false, onClick,}: SubmitButtonProps) { return ( type="submit" disabled={disabled || isLoading} onClick={onClick} aria-busy={isLoading} > {isLoading && ( className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" /> )} {isLoading ? 'Submitting...' : children} )}Testing a Form Component
// components/login-form.tsx'use client'import { useState } from 'react'import { Button } from '@/components/ui/button'import { Input } from '@/components/ui/input'import { Label } from '@/components/ui/label'interface LoginFormProps { onSubmit: (data: { email: string; password: string }) => Promise }export function LoginForm({ onSubmit }: LoginFormProps) { const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [error, setError] = useState(null) const [isLoading, setIsLoading] = useState(false) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() setError(null) if (!email || !password) { setError('Please fill in all fields') return } setIsLoading(true) try { await onSubmit({ email, password }) } catch (err) { setError('Invalid credentials') } finally { setIsLoading(false) } } return ( > >Email id="email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="you@example.com" /> >Password id="password" type="password" value={password} onChange={(e) => setPassword(e.target.value)} /> {error && ( "alert"
className="text-sm text-red-500"> {error} )} ="submit" disabled={isLoading} className="w-full"> {isLoading ? 'Signing in...' : 'Sign in'} )}Query Priority
React Testing Library provides various queries. Use them in this priority order for the most accessible tests:
Queries accessible to everyone (buttons, links, headings, etc.)
For form fields - ensures labels are properly associated
When there's no label (though labels are preferred)
For non-interactive elements with text content
Only when no other query works - requires adding data-testid attributes
Running Component Tests
# Run all component testsnpm run test:component# Run in watch modenpm run test:component -- --watch# Run specific component testnpm run test:component -- login-form.test.tsx