mirror of
https://github.com/vitest-dev/vitest.git
synced 2026-02-01 17:36:51 +00:00
docs: add comprehensive Component Testing guide (#8409)
Co-authored-by: Rinil Kunhiraman <rinilkunhiraman@users.noreply.github.com> Co-authored-by: Vladimir <sleuths.slews0s@icloud.com>
This commit is contained in:
parent
ac1d92f140
commit
482ee64815
@ -295,6 +295,11 @@ export default ({ mode }: { mode: string }) => {
|
||||
link: '/guide/browser/multiple-setups',
|
||||
docFooterText: 'Multiple Setups | Browser Mode',
|
||||
},
|
||||
{
|
||||
text: 'Component Testing',
|
||||
link: '/guide/browser/component-testing',
|
||||
docFooterText: 'Component Testing | Browser Mode',
|
||||
},
|
||||
{
|
||||
text: 'Visual Regression Testing',
|
||||
link: '/guide/browser/visual-regression-testing',
|
||||
|
||||
575
docs/guide/browser/component-testing.md
Normal file
575
docs/guide/browser/component-testing.md
Normal file
@ -0,0 +1,575 @@
|
||||
---
|
||||
title: Component Testing | Guide
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# Component Testing
|
||||
|
||||
Component testing is a testing strategy that focuses on testing individual UI components in isolation. Unlike end-to-end tests that test entire user flows, component tests verify that each component works correctly on its own, making them faster to run and easier to debug.
|
||||
|
||||
Vitest provides comprehensive support for component testing across multiple frameworks including Vue, React, Svelte, Lit, Preact, Qwik, Solid, Marko, and more. This guide covers the specific patterns, tools, and best practices for testing components effectively with Vitest.
|
||||
|
||||
## Why Component Testing?
|
||||
|
||||
Component testing sits between unit tests and end-to-end tests, offering several advantages:
|
||||
|
||||
- **Faster feedback** - Test individual components without loading entire applications
|
||||
- **Isolated testing** - Focus on component behavior without external dependencies
|
||||
- **Better debugging** - Easier to pinpoint issues in specific components
|
||||
- **Comprehensive coverage** - Test edge cases and error states more easily
|
||||
|
||||
## Browser Mode for Component Testing
|
||||
|
||||
Component testing in Vitest uses **Browser Mode** to run tests in real browser environments using Playwright, WebdriverIO, or preview mode. This provides the most accurate testing environment as your components run in real browsers with actual DOM implementations, CSS rendering, and browser APIs.
|
||||
|
||||
### Why Browser Mode?
|
||||
|
||||
Browser Mode is the recommended approach for component testing because it provides the most accurate testing environment. Unlike DOM simulation libraries, Browser Mode catches real-world issues that can affect your users.
|
||||
|
||||
::: tip
|
||||
Browser Mode catches issues that DOM simulation libraries might miss, including:
|
||||
- CSS layout and styling problems
|
||||
- Real browser API behavior
|
||||
- Accurate event handling and propagation
|
||||
- Proper focus management and accessibility features
|
||||
|
||||
:::
|
||||
|
||||
### Purpose of this Guide
|
||||
|
||||
This guide focuses specifically on **component testing patterns and best practices** using Vitest's capabilities. While many examples use Browser Mode (as it's the recommended approach), the focus here is on component-specific testing strategies rather than browser configuration details.
|
||||
|
||||
For detailed browser setup, configuration options, and advanced browser features, refer to the [Browser Mode documentation](/guide/browser/).
|
||||
|
||||
## What Makes a Good Component Test
|
||||
|
||||
Good component tests focus on **behavior and user experience** rather than implementation details:
|
||||
|
||||
- **Test the contract** - How components receive inputs (props) and produce outputs (events, renders)
|
||||
- **Test user interactions** - Clicks, form submissions, keyboard navigation
|
||||
- **Test edge cases** - Error states, loading states, empty states
|
||||
- **Avoid testing internals** - State variables, private methods, CSS classes
|
||||
|
||||
### Component Testing Hierarchy
|
||||
|
||||
```
|
||||
1. Critical User Paths → Always test these
|
||||
2. Error Handling → Test failure scenarios
|
||||
3. Edge Cases → Empty data, extreme values
|
||||
4. Accessibility → Screen readers, keyboard nav
|
||||
5. Performance → Large datasets, animations
|
||||
```
|
||||
|
||||
## Component Testing Strategies
|
||||
|
||||
### Isolation Strategy
|
||||
|
||||
Test components in isolation by mocking dependencies:
|
||||
|
||||
```tsx
|
||||
// For API requests, we recommend MSW (Mock Service Worker)
|
||||
// See: https://vitest.dev/guide/mocking/requests
|
||||
//
|
||||
// vi.mock(import('../api/userService'), () => ({
|
||||
// fetchUser: vi.fn().mockResolvedValue({ name: 'John' })
|
||||
// }))
|
||||
|
||||
// Mock child components to focus on parent logic
|
||||
vi.mock(import('../components/UserCard'), () => ({
|
||||
default: vi.fn(({ user }) => `<div>User: ${user.name}</div>`)
|
||||
}))
|
||||
|
||||
test('UserProfile handles loading and data states', async () => {
|
||||
const { getByText } = render(<UserProfile userId="123" />)
|
||||
|
||||
// Test loading state
|
||||
await expect.element(getByText('Loading...')).toBeInTheDocument()
|
||||
|
||||
// Test for data to load (expect.element auto-retries)
|
||||
await expect.element(getByText('User: John')).toBeInTheDocument()
|
||||
})
|
||||
```
|
||||
|
||||
### Integration Strategy
|
||||
|
||||
Test component collaboration and data flow:
|
||||
|
||||
```tsx
|
||||
test('ProductList filters and displays products correctly', async () => {
|
||||
const mockProducts = [
|
||||
{ id: 1, name: 'Laptop', category: 'Electronics', price: 999 },
|
||||
{ id: 2, name: 'Book', category: 'Education', price: 29 }
|
||||
]
|
||||
|
||||
const { getByLabelText, getByText } = render(
|
||||
<ProductList products={mockProducts} />
|
||||
)
|
||||
|
||||
// Initially shows all products
|
||||
await expect.element(getByText('Laptop')).toBeInTheDocument()
|
||||
await expect.element(getByText('Book')).toBeInTheDocument()
|
||||
|
||||
// Filter by category
|
||||
await userEvent.selectOptions(
|
||||
getByLabelText(/category/i),
|
||||
'Electronics'
|
||||
)
|
||||
|
||||
// Only electronics should remain
|
||||
await expect.element(getByText('Laptop')).toBeInTheDocument()
|
||||
await expect.element(queryByText('Book')).not.toBeInTheDocument()
|
||||
})
|
||||
```
|
||||
|
||||
## Testing Library Integration
|
||||
|
||||
While Vitest provides official packages for popular frameworks ([`vitest-browser-vue`](https://www.npmjs.com/package/vitest-browser-vue), [`vitest-browser-react`](https://www.npmjs.com/package/vitest-browser-react), [`vitest-browser-svelte`](https://www.npmjs.com/package/vitest-browser-svelte)), you can integrate with [Testing Library](https://testing-library.com/) for frameworks not yet officially supported.
|
||||
|
||||
### When to Use Testing Library
|
||||
|
||||
- Your framework doesn't have an official Vitest browser package yet
|
||||
- You're migrating existing tests that use Testing Library
|
||||
- You prefer Testing Library's API for specific testing scenarios
|
||||
|
||||
### Integration Pattern
|
||||
|
||||
The key is using `page.elementLocator()` to bridge Testing Library's DOM output with Vitest's browser mode APIs:
|
||||
|
||||
```jsx
|
||||
// For Solid.js components
|
||||
import { render } from '@testing-library/solid'
|
||||
import { page } from '@vitest/browser/context'
|
||||
|
||||
test('Solid component handles user interaction', async () => {
|
||||
// Use Testing Library to render the component
|
||||
const { baseElement, getByRole } = render(() =>
|
||||
<Counter initialValue={0} />
|
||||
)
|
||||
|
||||
// Bridge to Vitest's browser mode for interactions and assertions
|
||||
const screen = page.elementLocator(baseElement)
|
||||
|
||||
// Use Vitest's page queries for finding elements
|
||||
const incrementButton = screen.getByRole('button', { name: /increment/i })
|
||||
|
||||
// Use Vitest's assertions and interactions
|
||||
await expect.element(screen.getByText('Count: 0')).toBeInTheDocument()
|
||||
|
||||
// Trigger user interaction using Vitest's page API
|
||||
await incrementButton.click()
|
||||
|
||||
await expect.element(screen.getByText('Count: 1')).toBeInTheDocument()
|
||||
})
|
||||
```
|
||||
|
||||
### Available Testing Library Packages
|
||||
|
||||
Popular Testing Library packages that work well with Vitest:
|
||||
|
||||
- [`@testing-library/solid`](https://github.com/solidjs/solid-testing-library) - For Solid.js
|
||||
- [`@marko/testing-library`](https://testing-library.com/docs/marko-testing-library/intro) - For Marko
|
||||
- [`@testing-library/svelte`](https://testing-library.com/docs/svelte-testing-library/intro) - Alternative to [`vitest-browser-svelte`](https://www.npmjs.com/package/vitest-browser-svelte)
|
||||
- [`@testing-library/vue`](https://testing-library.com/docs/vue-testing-library/intro) - Alternative to [`vitest-browser-vue`](https://www.npmjs.com/package/vitest-browser-vue)
|
||||
|
||||
::: tip Migration Path
|
||||
If your framework gets official Vitest support later, you can gradually migrate by replacing Testing Library's `render` function while keeping most of your test logic intact.
|
||||
:::
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Use Browser Mode for CI/CD
|
||||
Ensure tests run in real browser environments for the most accurate testing. Browser Mode provides accurate CSS rendering, real browser APIs, and proper event handling.
|
||||
|
||||
### 2. Test User Interactions
|
||||
Simulate real user behavior using Vitest's [Interactivity API](/guide/browser/interactivity-api). Use `page.getByRole()` and `userEvent` methods as shown in our [Advanced Testing Patterns](#advanced-testing-patterns):
|
||||
|
||||
```tsx
|
||||
// Good: Test actual user interactions
|
||||
await page.getByRole('button', { name: /submit/i }).click()
|
||||
await page.getByLabelText(/email/i).fill('user@example.com')
|
||||
|
||||
// Avoid: Testing implementation details
|
||||
// component.setState({ email: 'user@example.com' })
|
||||
```
|
||||
|
||||
### 3. Test Accessibility
|
||||
Ensure components work for all users by testing keyboard navigation, focus management, and ARIA attributes. See our [Testing Accessibility](#testing-accessibility) example for practical patterns:
|
||||
|
||||
```tsx
|
||||
// Test keyboard navigation
|
||||
await userEvent.keyboard('{Tab}')
|
||||
await expect.element(document.activeElement).toHaveFocus()
|
||||
|
||||
// Test ARIA attributes
|
||||
await expect.element(modal).toHaveAttribute('aria-modal', 'true')
|
||||
```
|
||||
|
||||
### 4. Mock External Dependencies
|
||||
Focus tests on component logic by mocking APIs and external services. This makes tests faster and more reliable. See our [Isolation Strategy](#isolation-strategy) for examples:
|
||||
|
||||
```tsx
|
||||
// For API requests, we recommend using MSW (Mock Service Worker)
|
||||
// See: https://vitest.dev/guide/mocking/requests
|
||||
// This provides more realistic request/response mocking
|
||||
|
||||
// For module mocking, use the import() syntax
|
||||
vi.mock(import('../components/UserCard'), () => ({
|
||||
default: vi.fn(() => <div>Mocked UserCard</div>)
|
||||
}))
|
||||
```
|
||||
|
||||
### 5. Use Meaningful Test Descriptions
|
||||
Write test descriptions that explain the expected behavior, not implementation details:
|
||||
|
||||
```tsx
|
||||
// Good: Describes user-facing behavior
|
||||
test('shows error message when email format is invalid')
|
||||
test('disables submit button while form is submitting')
|
||||
|
||||
// Avoid: Implementation-focused descriptions
|
||||
test('calls validateEmail function')
|
||||
test('sets isSubmitting state to true')
|
||||
```
|
||||
|
||||
## Advanced Testing Patterns
|
||||
|
||||
### Testing Component State Management
|
||||
|
||||
```tsx
|
||||
// Testing stateful components and state transitions
|
||||
test('ShoppingCart manages items correctly', async () => {
|
||||
const { getByText, getByTestId } = render(<ShoppingCart />)
|
||||
|
||||
// Initially empty
|
||||
await expect.element(getByText('Your cart is empty')).toBeInTheDocument()
|
||||
|
||||
// Add item
|
||||
await page.getByRole('button', { name: /add laptop/i }).click()
|
||||
|
||||
// Verify state change
|
||||
await expect.element(getByText('1 item')).toBeInTheDocument()
|
||||
await expect.element(getByText('Laptop - $999')).toBeInTheDocument()
|
||||
|
||||
// Test quantity updates
|
||||
await page.getByRole('button', { name: /increase quantity/i }).click()
|
||||
await expect.element(getByText('2 items')).toBeInTheDocument()
|
||||
})
|
||||
```
|
||||
|
||||
### Testing Async Components with Data Fetching
|
||||
|
||||
```tsx
|
||||
// Option 1: Recommended - Use MSW (Mock Service Worker) for API mocking
|
||||
import { http, HttpResponse } from 'msw'
|
||||
import { setupServer } from 'msw/node'
|
||||
|
||||
// Set up MSW server with API handlers
|
||||
const server = setupServer(
|
||||
http.get('/api/users/:id', ({ params }) => {
|
||||
const { id } = params
|
||||
if (id === '123') {
|
||||
return HttpResponse.json({ name: 'John Doe', email: 'john@example.com' })
|
||||
}
|
||||
return HttpResponse.json({ error: 'User not found' }, { status: 404 })
|
||||
})
|
||||
)
|
||||
|
||||
// Start server before all tests
|
||||
beforeAll(() => server.listen())
|
||||
afterEach(() => server.resetHandlers())
|
||||
afterAll(() => server.close())
|
||||
|
||||
test('UserProfile handles loading, success, and error states', async () => {
|
||||
// Test success state
|
||||
const { getByText } = render(<UserProfile userId="123" />)
|
||||
// expect.element auto-retries until elements are found
|
||||
await expect.element(getByText('John Doe')).toBeInTheDocument()
|
||||
await expect.element(getByText('john@example.com')).toBeInTheDocument()
|
||||
|
||||
// Test error state by overriding the handler for this test
|
||||
server.use(
|
||||
http.get('/api/users/:id', () => {
|
||||
return HttpResponse.json({ error: 'User not found' }, { status: 404 })
|
||||
})
|
||||
)
|
||||
|
||||
const { getByText: getErrorText } = render(<UserProfile userId="999" />)
|
||||
await expect.element(getErrorText('Error: User not found')).toBeInTheDocument()
|
||||
})
|
||||
```
|
||||
|
||||
### Testing Component Communication
|
||||
|
||||
```tsx
|
||||
// Test parent-child component interaction
|
||||
test('parent and child components communicate correctly', async () => {
|
||||
const mockOnSelectionChange = vi.fn()
|
||||
|
||||
const { getByText } = render(
|
||||
<ProductCatalog onSelectionChange={mockOnSelectionChange}>
|
||||
<ProductFilter />
|
||||
<ProductGrid />
|
||||
</ProductCatalog>
|
||||
)
|
||||
|
||||
// Interact with child component
|
||||
await page.getByRole('checkbox', { name: /electronics/i }).click()
|
||||
|
||||
// Verify parent receives the communication
|
||||
expect(mockOnSelectionChange).toHaveBeenCalledWith({
|
||||
category: 'electronics',
|
||||
filters: ['electronics']
|
||||
})
|
||||
|
||||
// Verify other child component updates (expect.element auto-retries)
|
||||
await expect.element(getByText('Showing Electronics products')).toBeInTheDocument()
|
||||
})
|
||||
```
|
||||
|
||||
### Testing Complex Forms with Validation
|
||||
|
||||
```tsx
|
||||
test('ContactForm handles complex validation scenarios', async () => {
|
||||
const mockSubmit = vi.fn()
|
||||
const { getByLabelText, getByText } = render(
|
||||
<ContactForm onSubmit={mockSubmit} />
|
||||
)
|
||||
|
||||
const nameInput = page.getByLabelText(/full name/i)
|
||||
const emailInput = page.getByLabelText(/email/i)
|
||||
const messageInput = page.getByLabelText(/message/i)
|
||||
const submitButton = page.getByRole('button', { name: /send message/i })
|
||||
|
||||
// Test validation triggers
|
||||
await submitButton.click()
|
||||
|
||||
await expect.element(getByText('Name is required')).toBeInTheDocument()
|
||||
await expect.element(getByText('Email is required')).toBeInTheDocument()
|
||||
await expect.element(getByText('Message is required')).toBeInTheDocument()
|
||||
|
||||
// Test partial validation
|
||||
await nameInput.fill('John Doe')
|
||||
await submitButton.click()
|
||||
|
||||
await expect.element(getByText('Name is required')).not.toBeInTheDocument()
|
||||
await expect.element(getByText('Email is required')).toBeInTheDocument()
|
||||
|
||||
// Test email format validation
|
||||
await emailInput.fill('invalid-email')
|
||||
await submitButton.click()
|
||||
|
||||
await expect.element(getByText('Please enter a valid email')).toBeInTheDocument()
|
||||
|
||||
// Test successful submission
|
||||
await emailInput.fill('john@example.com')
|
||||
await messageInput.fill('Hello, this is a test message.')
|
||||
await submitButton.click()
|
||||
|
||||
expect(mockSubmit).toHaveBeenCalledWith({
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
message: 'Hello, this is a test message.'
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Testing Error Boundaries
|
||||
|
||||
```tsx
|
||||
// Test how components handle and recover from errors
|
||||
function ThrowError({ shouldThrow }: { shouldThrow: boolean }) {
|
||||
if (shouldThrow) {
|
||||
throw new Error('Component error!')
|
||||
}
|
||||
return <div>Component working fine</div>
|
||||
}
|
||||
|
||||
test('ErrorBoundary catches and displays errors gracefully', async () => {
|
||||
const { getByText, rerender } = render(
|
||||
<ErrorBoundary fallback={<div>Something went wrong</div>}>
|
||||
<ThrowError shouldThrow={false} />
|
||||
</ErrorBoundary>
|
||||
)
|
||||
|
||||
// Initially working
|
||||
await expect.element(getByText('Component working fine')).toBeInTheDocument()
|
||||
|
||||
// Trigger error
|
||||
rerender(
|
||||
<ErrorBoundary fallback={<div>Something went wrong</div>}>
|
||||
<ThrowError shouldThrow={true} />
|
||||
</ErrorBoundary>
|
||||
)
|
||||
|
||||
// Error boundary should catch it
|
||||
await expect.element(getByText('Something went wrong')).toBeInTheDocument()
|
||||
})
|
||||
```
|
||||
|
||||
### Testing Accessibility
|
||||
|
||||
```tsx
|
||||
test('Modal component is accessible', async () => {
|
||||
const { getByRole, getByLabelText } = render(
|
||||
<Modal isOpen={true} title="Settings">
|
||||
<SettingsForm />
|
||||
</Modal>
|
||||
)
|
||||
|
||||
// Test focus management - modal should receive focus when opened
|
||||
// This is crucial for screen reader users to know a modal opened
|
||||
const modal = getByRole('dialog')
|
||||
await expect.element(modal).toHaveFocus()
|
||||
|
||||
// Test ARIA attributes - these provide semantic information to screen readers
|
||||
await expect.element(modal).toHaveAttribute('aria-labelledby') // Links to title element
|
||||
await expect.element(modal).toHaveAttribute('aria-modal', 'true') // Indicates modal behavior
|
||||
|
||||
// Test keyboard navigation - Escape key should close modal
|
||||
// This is required by ARIA authoring practices
|
||||
await userEvent.keyboard('{Escape}')
|
||||
// expect.element auto-retries until modal is removed
|
||||
await expect.element(modal).not.toBeInTheDocument()
|
||||
|
||||
// Test focus trap - tab navigation should cycle within modal
|
||||
// This prevents users from tabbing to content behind the modal
|
||||
const firstInput = getByLabelText(/username/i)
|
||||
const lastButton = getByRole('button', { name: /save/i })
|
||||
|
||||
// Use click to focus on the first input, then test tab navigation
|
||||
await firstInput.click()
|
||||
await userEvent.keyboard('{Shift>}{Tab}{/Shift}') // Shift+Tab goes backwards
|
||||
await expect.element(lastButton).toHaveFocus() // Should wrap to last element
|
||||
})
|
||||
```
|
||||
|
||||
## Debugging Component Tests
|
||||
|
||||
### 1. Use Browser Dev Tools
|
||||
|
||||
Browser Mode runs tests in real browsers, giving you access to full developer tools. When tests fail, you can:
|
||||
|
||||
- **Open browser dev tools** during test execution (F12 or right-click → Inspect)
|
||||
- **Set breakpoints** in your test code or component code
|
||||
- **Inspect the DOM** to see the actual rendered output
|
||||
- **Check console errors** for JavaScript errors or warnings
|
||||
- **Monitor network requests** to debug API calls
|
||||
|
||||
For headful mode debugging, add `headless: false` to your browser config temporarily.
|
||||
|
||||
### 2. Add Debug Statements
|
||||
|
||||
Use strategic logging to understand test failures:
|
||||
|
||||
```tsx
|
||||
test('debug form validation', async () => {
|
||||
render(<ContactForm />)
|
||||
|
||||
const submitButton = page.getByRole('button', { name: /submit/i })
|
||||
await submitButton.click()
|
||||
|
||||
// Debug: Check if element exists with different query
|
||||
const errorElement = page.getByText('Email is required')
|
||||
console.log('Error element found:', errorElement.length)
|
||||
|
||||
await expect.element(errorElement).toBeInTheDocument()
|
||||
})
|
||||
```
|
||||
|
||||
### 3. Inspect Rendered Output
|
||||
|
||||
When components don't render as expected, investigate systematically:
|
||||
|
||||
**Use Vitest's browser UI:**
|
||||
- Run tests with browser mode enabled
|
||||
- Open the browser URL shown in the terminal to see tests running
|
||||
- Visual inspection helps identify CSS issues, layout problems, or missing elements
|
||||
|
||||
**Test element queries:**
|
||||
```tsx
|
||||
// Debug why elements can't be found
|
||||
const button = page.getByRole('button', { name: /submit/i })
|
||||
console.log('Button count:', button.length) // Should be 1
|
||||
|
||||
// Try alternative queries if the first one fails
|
||||
if (button.length === 0) {
|
||||
console.log('All buttons:', page.getByRole('button').length)
|
||||
console.log('By test ID:', page.getByTestId('submit-btn').length)
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Verify Selectors
|
||||
|
||||
Selector issues are common causes of test failures. Debug them systematically:
|
||||
|
||||
**Check accessible names:**
|
||||
```tsx
|
||||
// If getByRole fails, check what roles/names are available
|
||||
const buttons = page.getByRole('button').all()
|
||||
for (const button of buttons) {
|
||||
// Use element() to get the DOM element and access native properties
|
||||
const element = button.element()
|
||||
const accessibleName = element.getAttribute('aria-label') || element.textContent
|
||||
console.log(`Button: "${accessibleName}"`)
|
||||
}
|
||||
```
|
||||
|
||||
**Test different query strategies:**
|
||||
```tsx
|
||||
// Multiple ways to find the same element using .or for auto-retrying
|
||||
const submitButton = page.getByRole('button', { name: /submit/i }) // By accessible name
|
||||
.or(page.getByTestId('submit-button')) // By test ID
|
||||
.or(page.getByText('Submit')) // By exact text
|
||||
// Note: Vitest doesn't have page.locator(), use specific getBy* methods instead
|
||||
```
|
||||
|
||||
**Common selector debugging patterns:**
|
||||
```tsx
|
||||
test('debug element queries', async () => {
|
||||
render(<LoginForm />)
|
||||
|
||||
// Check if element is visible and enabled
|
||||
const emailInput = page.getByLabelText(/email/i)
|
||||
await expect.element(emailInput).toBeVisible() // Will show if element is visible and print DOM if not
|
||||
})
|
||||
```
|
||||
|
||||
### 5. Debugging Async Issues
|
||||
|
||||
Component tests often involve timing issues:
|
||||
|
||||
```tsx
|
||||
test('debug async component behavior', async () => {
|
||||
render(<AsyncUserProfile userId="123" />)
|
||||
|
||||
// expect.element will automatically retry and show helpful error messages
|
||||
await expect.element(page.getByText('John Doe')).toBeInTheDocument()
|
||||
})
|
||||
```
|
||||
|
||||
## Migration from Other Testing Frameworks
|
||||
|
||||
### From Jest + Testing Library
|
||||
|
||||
Most Jest + Testing Library tests work with minimal changes:
|
||||
|
||||
```ts
|
||||
// Before (Jest)
|
||||
import { render, screen } from '@testing-library/react' // [!code --]
|
||||
|
||||
// After (Vitest)
|
||||
import { render } from 'vitest-browser-react' // [!code ++]
|
||||
```
|
||||
|
||||
### Key Differences
|
||||
|
||||
- Use `await expect.element()` instead of `expect()` for DOM assertions
|
||||
- Use `@vitest/browser/context` for user interactions instead of `@testing-library/user-event`
|
||||
- Browser Mode provides real browser environment for accurate testing
|
||||
|
||||
## Learn More
|
||||
|
||||
- [Browser Mode Documentation](/guide/browser/)
|
||||
- [Assertion API](/guide/browser/assertion-api)
|
||||
- [Interactivity API](/guide/browser/interactivity-api)
|
||||
- [Example Repository](https://github.com/vitest-tests/browser-examples)
|
||||
301
review.md
Normal file
301
review.md
Normal file
@ -0,0 +1,301 @@
|
||||
docs/.vitepress/config.ts
|
||||
|
||||
@@ -489,6 +489,10 @@ function guide(): DefaultTheme.SidebarItem[] {
|
||||
text: 'Test Projects',
|
||||
link: '/guide/projects',
|
||||
},
|
||||
{
|
||||
|
||||
Comment :
|
||||
sheremet-va 1 hour ago
|
||||
|
||||
From #8409 (comment)
|
||||
|
||||
This should be in the browser guides section (in the same level as "Multiple Setups")
|
||||
|
||||
--------------
|
||||
docs/guide/component-testing.md
|
||||
|
||||
### Component Testing Hierarchy
|
||||
|
||||
```
|
||||
1. Critical User Paths → Always test these
|
||||
|
||||
Comment:
|
||||
sheremet-va 1 hour ago
|
||||
|
||||
should the spaces be aligned? The first 2 arrows are not aligned
|
||||
-----------------------
|
||||
docs/guide/component-testing.md
|
||||
|
||||
```tsx
|
||||
// Mock external services
|
||||
vi.mock('../api/userService', () => ({
|
||||
|
||||
Comment:
|
||||
sheremet-va 1 hour ago
|
||||
we recommend vi.mock(import('../api/userService')) syntax (applied to all vi.mock calls here)
|
||||
--------------------------
|
||||
docs/guide/component-testing.md
|
||||
|
||||
expect(getByText('Loading...')).toBeInTheDocument()
|
||||
|
||||
// Wait for data to load
|
||||
await waitFor(() => {
|
||||
|
||||
Comment:
|
||||
sheremet-va 1 hour ago
|
||||
There is await expect.element(locator).toBeInTheDocument()
|
||||
|
||||
---------------------
|
||||
docs/guide/component-testing.md
|
||||
|
||||
)
|
||||
|
||||
// Initially shows all products
|
||||
expect(getByText('Laptop')).toBeInTheDocument()
|
||||
|
||||
Comment:
|
||||
sheremet-va 1 hour ago
|
||||
|
||||
We recommend all expect(locator) to be expect.element(locator)
|
||||
|
||||
-------------------------
|
||||
docs/guide/component-testing.md
|
||||
|
||||
const screen = page.elementLocator(baseElement)
|
||||
|
||||
// You can use either Testing Library queries or Vitest's page queries
|
||||
const incrementButton = getByRole('button', { name: /increment/i })
|
||||
|
||||
Comment:
|
||||
sheremet-va 1 hour ago
|
||||
I don't think we should introduce this confusion. Don't use testing-libraries' getBy* methods anywhere
|
||||
|
||||
----------------------------
|
||||
docs/guide/component-testing.md
|
||||
|
||||
expect(document.activeElement).toBe(nextFocusableElement)
|
||||
|
||||
// Test ARIA attributes
|
||||
expect(modal).toHaveAttribute('aria-modal', 'true')
|
||||
|
||||
Comment:
|
||||
sheremet-va 1 hour ago
|
||||
we recommend await expect.element(el).toHaveAttribute() (notice await) because it auto-retries the assertion
|
||||
--------------------------
|
||||
docs/guide/component-testing.md
|
||||
|
||||
```tsx
|
||||
// Mock API calls
|
||||
vi.mock('../api/userService', () => ({
|
||||
|
||||
Comment :
|
||||
sheremet-va 1 hour ago
|
||||
|
||||
For APIs we recommend msw (you can link /guide/mocking/requests)
|
||||
|
||||
---------------------------
|
||||
docs/guide/component-testing.md
|
||||
|
||||
```tsx
|
||||
// Mock the API to test different scenarios
|
||||
const mockUserApi = vi.fn()
|
||||
vi.mock('../api/users', () => ({ getUser: mockUserApi }))
|
||||
|
||||
Comment:
|
||||
sheremet-va 1 hour ago
|
||||
This code doesn't work, it will throw ReferenceError. Requests examples should use msw
|
||||
|
||||
-----------------
|
||||
docs/guide/component-testing.md
|
||||
|
||||
mockUserApi.mockResolvedValue({ name: 'John Doe', email: 'john@example.com' })
|
||||
rerender(<UserProfile userId="123" />)
|
||||
|
||||
await waitFor(() => {
|
||||
|
||||
Comment:
|
||||
sheremet-va 1 hour ago
|
||||
|
||||
do not use waitFor anywhere. Vitest supports auto-retrying via expect.element
|
||||
|
||||
------------------------
|
||||
docs/guide/component-testing.md
|
||||
|
||||
await expect.element(getByText('Please enter a valid email')).toBeInTheDocument()
|
||||
|
||||
// Test successful submission
|
||||
await emailInput.clear()
|
||||
|
||||
Comment:
|
||||
sheremet-va 1 hour ago
|
||||
fill already does the clear so it's redundant
|
||||
|
||||
-------------------
|
||||
docs/guide/component-testing.md
|
||||
|
||||
const firstInput = getByLabelText(/username/i)
|
||||
const lastButton = getByRole('button', { name: /save/i })
|
||||
|
||||
firstInput.focus()
|
||||
|
||||
Comment:
|
||||
sheremet-va 1 hour ago
|
||||
Vitest doesn't have a focus method - I already mentioned it before: #8409 (comment)
|
||||
|
||||
------------------------
|
||||
docs/guide/component-testing.md
|
||||
|
||||
firstInput.focus()
|
||||
await userEvent.keyboard('{Shift>}{Tab}{/Shift}') // Shift+Tab goes backwards
|
||||
expect(document.activeElement).toBe(lastButton) // Should wrap to last element
|
||||
|
||||
Comment:
|
||||
sheremet-va 1 hour ago
|
||||
await expect.element
|
||||
|
||||
-----------------------
|
||||
docs/guide/component-testing.md
|
||||
|
||||
- **Check console errors** for JavaScript errors or warnings
|
||||
- **Monitor network requests** to debug API calls
|
||||
|
||||
For headless mode debugging, add `headless: false` to your browser config temporarily.
|
||||
|
||||
Comment:
|
||||
sheremet-va 1 hour ago
|
||||
|
||||
For headless mode debugging
|
||||
|
||||
for non-headless or headful
|
||||
|
||||
it can't be headless if you set headless: false
|
||||
|
||||
--------------------------
|
||||
docs/guide/component-testing.md
|
||||
|
||||
// Debug: Check if element exists with different query
|
||||
const errorElement = page.getByText('Email is required')
|
||||
console.log('Error element found:', await errorElement.count())
|
||||
|
||||
Comment :
|
||||
sheremet-va 1 hour ago
|
||||
|
||||
vitest doesn't have a count, we do have length though
|
||||
|
||||
-------------------------
|
||||
docs/guide/component-testing.md
|
||||
|
||||
```tsx
|
||||
// Debug why elements can't be found
|
||||
const button = page.getByRole('button', { name: /submit/i })
|
||||
console.log('Button count:', await button.count()) // Should be 1
|
||||
|
||||
Comment:
|
||||
sheremet-va 1 hour ago
|
||||
no count
|
||||
|
||||
-------------------------
|
||||
docs/guide/component-testing.md
|
||||
|
||||
// Try alternative queries if the first one fails
|
||||
if (await button.count() === 0) {
|
||||
console.log('All buttons:', await page.getByRole('button').all())
|
||||
|
||||
Comment:
|
||||
sheremet-va 1 hour ago
|
||||
|
||||
all is not async
|
||||
|
||||
-----------------------
|
||||
docs/guide/component-testing.md
|
||||
// If getByRole fails, check what roles/names are available
|
||||
const buttons = await page.getByRole('button').all()
|
||||
for (const button of buttons) {
|
||||
const accessibleName = await button.getAttribute('aria-label')
|
||||
|
||||
Comment:
|
||||
sheremet-va 1 hour ago
|
||||
|
||||
there is no getAttribute
|
||||
|
||||
-----------------------------
|
||||
docs/guide/component-testing.md
|
||||
|
||||
const buttons = await page.getByRole('button').all()
|
||||
for (const button of buttons) {
|
||||
const accessibleName = await button.getAttribute('aria-label')
|
||||
|| await button.textContent()
|
||||
|
||||
Comment:
|
||||
sheremet-va 1 hour ago
|
||||
|
||||
there is no textContent
|
||||
|
||||
---------------------------
|
||||
docs/guide/component-testing.md
|
||||
|
||||
const submitButton
|
||||
= page.getByRole('button', { name: /submit/i }) // By accessible name
|
||||
|| page.getByTestId('submit-button') // By test ID
|
||||
|| page.locator('button[type="submit"]') // By CSS selector
|
||||
|
||||
Comment:
|
||||
sheremet-va 1 hour ago
|
||||
|
||||
there is no locator
|
||||
|
||||
---------------------
|
||||
docs/guide/component-testing.md
|
||||
|
||||
// 3. Check if element is hidden or disabled
|
||||
if (await emailInput.count() > 0) {
|
||||
console.log('Email input visible:', await emailInput.isVisible())
|
||||
|
||||
sheremet-va 1 hour ago
|
||||
|
||||
there is no isVisible
|
||||
|
||||
Note For you:
|
||||
Always ensure that the methods exists in the library. Verify if you are unsure either within the repo or going online or ask me if you have any questions
|
||||
|
||||
Comment I shared for you
|
||||
I have put up all the reviews we got from @sheremet-va today . Here is the link to the file => /Users/cr7/Documents/review.md and there is note for you at the end of the markdown. Please ensure that you follow the notes. You need to fix each of the reviews one by one then you also have to provide me with the thoughtful response for @sheremet-va for each of the reviews.
|
||||
|
||||
---
|
||||
|
||||
## CURRENT STATUS & TODO LIST
|
||||
|
||||
### ✅ **Already Fixed (3/17 items):**
|
||||
1. **Hierarchy alignment** - Fixed arrow alignment in Component Testing Hierarchy
|
||||
2. **vi.mock syntax** - Updated to use `vi.mock(import('...'))` syntax throughout
|
||||
3. **MSW recommendation** - Added recommendation to use MSW for API mocking with link to `/guide/mocking/requests`
|
||||
|
||||
### 🚧 **High Priority - API Issues (Need Immediate Fix):**
|
||||
4. **Move component testing to browser guides section** - Config.ts placement issue
|
||||
5. **Replace expect() with expect.element()** - Line 41 & 56 + multiple other instances
|
||||
6. **Remove Testing Library getBy* confusion** - Line 69 - causes confusion with Vitest APIs
|
||||
7. **Add await to expect.element assertions** - Line 78 - for auto-retry functionality
|
||||
8. **Remove waitFor usage** - Line 116 - replace with expect.element auto-retry
|
||||
9. **Remove redundant clear() call** - Line 129 - fill() already clears
|
||||
10. **Fix focus method issue** - Line 141 - Vitest doesn't have focus method
|
||||
11. **Fix headless debugging text** - Line 164 - terminology correction
|
||||
12. **Replace count() with length** - Line 181, 194, 204 - count() doesn't exist in Vitest
|
||||
13. **Fix all() method usage** - Line 205, 215 - all() is not async
|
||||
14. **Replace getAttribute method** - Line 217 - doesn't exist in Vitest
|
||||
15. **Replace textContent method** - Line 230 - doesn't exist in Vitest
|
||||
16. **Replace locator method** - Line 241 - doesn't exist in Vitest
|
||||
17. **Replace isVisible method** - Line 256 - doesn't exist in Vitest
|
||||
|
||||
### 📝 **Next Steps:**
|
||||
1. Fix each issue systematically (one by one as requested)
|
||||
2. Prepare thoughtful response for each review comment to @sheremet-va
|
||||
3. Verify all API methods exist in Vitest browser mode documentation
|
||||
4. Test examples to ensure they work with actual Vitest APIs
|
||||
|
||||
### 🎯 **Root Cause Analysis:**
|
||||
The guide appears to have been written using Playwright/Testing Library APIs instead of the actual Vitest browser mode APIs. Many method names and patterns need to be corrected to match Vitest's implementation.
|
||||
|
||||
**Status as of:** September 3, 2025 11:41 AM
|
||||
**Completion:** 3/17 issues resolved (17.6%)
|
||||
Loading…
x
Reference in New Issue
Block a user