Command Palette

Search for a command to run...

Component Testing

Vitest + RTL

Test 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.

User-Centric

Test components the way users interact with them, not implementation details.

Accessibility

RTL queries encourage accessible markup and proper ARIA usage.

DOM Testing

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
// 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
// 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 (
"space-y-4">
>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}

)}
{isLoading ? 'Signing in...' : 'Sign in'}
)
}

Query Priority

React Testing Library provides various queries. Use them in this priority order for the most accessible tests:

1stgetByRole

Queries accessible to everyone (buttons, links, headings, etc.)

2ndgetByLabelText

For form fields - ensures labels are properly associated

3rdgetByPlaceholderText

When there's no label (though labels are preferred)

4thgetByText

For non-interactive elements with text content

LastgetByTestId

Only when no other query works - requires adding data-testid attributes

Running Component Tests

Terminal
# Run all component tests
npm run test:component
# Run in watch mode
npm run test:component -- --watch
# Run specific component test
npm run test:component -- login-form.test.tsx