Component Testing
Component testing guide for DocBits table components.
Overview
Component tests verify that components work correctly in realistic scenarios with multiple interactions.
Coverage:
- 42+ component tests for RowSelectionCheckbox
- 22+ component tests for BulkActionsMenu
- Integration tests for all components
Testing Setup
Same as Unit Testing.
Testing Pattern: Component Integration
Test multiple components working together:
typescript
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import DocBitsTable from '@/components/Table/DocBitsTable.vue'
describe('DocBitsTable - Component Integration', () => {
const columns = [
{ name: 'id', label: 'ID', field: 'id' },
{ name: 'name', label: 'Name', field: 'name' }
]
const rows = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
]
it('renders table with headers and data', () => {
const wrapper = mount(DocBitsTable, {
props: { rows, columns, rowKey: 'id' }
})
// Verify table structure
expect(wrapper.find('table').exists()).toBe(true)
expect(wrapper.findAll('th')).toHaveLength(2)
expect(wrapper.findAll('tbody tr')).toHaveLength(2)
})
it('handles row click events', async () => {
const wrapper = mount(DocBitsTable, {
props: { rows, columns, rowKey: 'id' }
})
const firstRow = wrapper.find('tbody tr')
await firstRow.trigger('click')
expect(wrapper.emitted('row-click')).toBeTruthy()
})
})Testing Pattern: Feature Flags
Test components with different feature combinations:
typescript
describe('DocBitsTable - Feature Toggles', () => {
it('shows sorting when enabled', () => {
const wrapper = mount(DocBitsTable, {
props: {
rows: [],
columns: [{ name: 'id', label: 'ID', field: 'id', sortable: true }],
features: { sorting: true }
}
})
expect(wrapper.find('.sort-arrow').exists()).toBe(true)
})
it('hides sorting when disabled', () => {
const wrapper = mount(DocBitsTable, {
props: {
rows: [],
columns: [{ name: 'id', label: 'ID', field: 'id' }],
features: { sorting: false }
}
})
expect(wrapper.find('.sort-arrow').exists()).toBe(false)
})
it('shows pagination when enabled', () => {
const wrapper = mount(DocBitsTable, {
props: {
rows: [],
columns: [],
features: { pagination: true }
}
})
expect(wrapper.find('.pagination').exists()).toBe(true)
})
})Testing Pattern: User Workflows
Test realistic user workflows:
typescript
describe('DocBitsTable - User Workflow: Select and Delete', () => {
it('completes select and delete workflow', async () => {
const rows = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' }
]
const wrapper = mount(DocBitsTable, {
props: {
rows,
columns: [{ name: 'id', label: 'ID', field: 'id' }],
rowKey: 'id',
features: { selection: true, bulkActions: true }
}
})
// Step 1: Select first row
const checkbox = wrapper.find('input[type="checkbox"]')
await checkbox.trigger('click')
expect(wrapper.emitted('selection-change')).toBeTruthy()
// Step 2: Verify bulk action menu appears
expect(wrapper.find('.bulk-actions-menu').exists()).toBe(true)
// Step 3: Trigger delete
const deleteBtn = wrapper.find('[aria-label*="Delete"]')
await deleteBtn.trigger('click')
expect(wrapper.emitted('bulk-action')).toBeTruthy()
})
})Testing Pattern: Async Operations
Test async operations like data loading:
typescript
describe('DocBitsTable - Async Operations', () => {
it('handles loading state', async () => {
const wrapper = mount(DocBitsTable, {
props: {
rows: [],
columns: [],
loading: false
}
})
// Initially no spinner
expect(wrapper.find('.loading-spinner').exists()).toBe(false)
// Update to loading
await wrapper.setProps({ loading: true })
expect(wrapper.find('.loading-spinner').exists()).toBe(true)
// Update to done
await wrapper.setProps({ loading: false })
expect(wrapper.find('.loading-spinner').exists()).toBe(false)
})
it('handles async data updates', async () => {
const initialRows = [{ id: 1, name: 'Item 1' }]
const updatedRows = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
]
const wrapper = mount(DocBitsTable, {
props: {
rows: initialRows,
columns: [{ name: 'id', label: 'ID', field: 'id' }],
rowKey: 'id'
}
})
expect(wrapper.findAll('tbody tr')).toHaveLength(1)
// Update rows
await wrapper.setProps({ rows: updatedRows })
expect(wrapper.findAll('tbody tr')).toHaveLength(2)
})
})Testing Pattern: Accessibility
Test accessibility features:
typescript
describe('DocBitsTable - Accessibility', () => {
it('has accessible table structure', () => {
const wrapper = mount(DocBitsTable, {
props: {
rows: [{ id: 1, name: 'Item' }],
columns: [
{ name: 'id', label: 'ID', field: 'id' },
{ name: 'name', label: 'Name', field: 'name' }
],
rowKey: 'id'
}
})
// Check for proper table roles
expect(wrapper.find('table').exists()).toBe(true)
expect(wrapper.find('thead').exists()).toBe(true)
expect(wrapper.find('tbody').exists()).toBe(true)
})
it('has keyboard navigation support', async () => {
const wrapper = mount(DocBitsTable, {
props: {
rows: [{ id: 1 }, { id: 2 }],
columns: [{ name: 'id', label: 'ID', field: 'id' }],
rowKey: 'id',
features: { selection: true }
}
})
// Tab to checkbox
const checkbox = wrapper.find('input[type="checkbox"]')
expect(checkbox.exists()).toBe(true)
// Space to select
await checkbox.trigger('keydown', { key: ' ' })
expect(wrapper.emitted('selection-change')).toBeTruthy()
})
})Testing Pattern: Dark Mode
Test component behavior in dark mode:
typescript
describe('DocBitsTable - Dark Mode', () => {
it('applies correct colors in dark mode', () => {
const wrapper = mount(DocBitsTable, {
props: {
rows: [],
columns: []
},
attachTo: document.body
})
// Apply dark mode
document.body.classList.add('dark')
const styles = window.getComputedStyle(wrapper.element)
expect(styles.backgroundColor).toBe('rgb(26, 26, 26)') // #1a1a1a
})
})Best Practices
1. Test User-Facing Behavior
typescript
// ✅ Good: Tests what user experiences
it('selects row when checkbox clicked', async () => {
const wrapper = mount(RowSelectionCheckbox, { props: { ... } })
await wrapper.find('input').trigger('click')
expect(wrapper.emitted('toggle')).toBeTruthy()
})
// ❌ Bad: Tests internal state
it('updates internal selectionState', async () => {
const wrapper = mount(RowSelectionCheckbox, { props: { ... } })
await wrapper.find('input').trigger('click')
expect(wrapper.vm.internalState).toBe(true)
})2. Test Complete Workflows
typescript
// ✅ Good: Complete workflow
it('adds, selects, and exports rows', async () => {
// Setup
const wrapper = mount(Table, { props: { ... } })
// Add row
await wrapper.find('[aria-label="Add"]').trigger('click')
// Select row
const checkbox = wrapper.find('input')
await checkbox.trigger('click')
// Export
const exportBtn = wrapper.find('[aria-label="Export"]')
await exportBtn.trigger('click')
expect(wrapper.emitted('bulk-action')).toBeTruthy()
})3. Mock External Dependencies
typescript
// Mock API calls
vi.mock('@/api', () => ({
fetchRows: vi.fn(() => Promise.resolve([{ id: 1 }]))
}))
it('loads data on mount', async () => {
const wrapper = mount(DocBitsTable)
await wrapper.vm.$nextTick()
expect(fetchRows).toHaveBeenCalled()
})Common Issues
Issue: "Element not found" errors
Cause: Component hasn't rendered yet
Solution: Wait for updates with nextTick:
typescript
await wrapper.vm.$nextTick() // Wait for Vue updates
await new Promise(r => setTimeout(r, 0)) // Wait for next tickIssue: Event not emitted
Cause: Wrong selector or trigger
Solution: Verify element exists and trigger correctly:
typescript
// First verify element exists
expect(wrapper.find('button').exists()).toBe(true)
// Then trigger with correct event
await wrapper.find('button').trigger('click')
// Check emission
expect(wrapper.emitted('click')).toBeTruthy()Issue: Props not updating
Cause: Need to await setProps
Solution: Always await prop changes:
typescript
await wrapper.setProps({ loading: true })
expect(wrapper.find('.spinner').exists()).toBe(true)Running Component Tests
bash
# Run all component tests
npm run test
# Run specific test file
npm run test -- src/components/Table/DocBitsTable.spec.ts
# Run with coverage
npm run test:coverage
# Watch mode for development
npm run test:watchRelated Resources
- Unit Testing - Unit test patterns
- Examples - Real test examples
- Components - Component documentation