Testing Examples
Real-world test code examples from the DocBits component library.
RowSelectionCheckbox Tests
Complete test suite for the row selection checkbox component (42+ tests).
Basic Selection
typescript
import { describe, it, expect, beforeEach } from 'vitest'
import { mount } from '@vue/test-utils'
import RowSelectionCheckbox from '@/components/Table/RowSelectionCheckbox.vue'
describe('RowSelectionCheckbox - Basic Selection', () => {
it('renders as unchecked by default', () => {
const wrapper = mount(RowSelectionCheckbox, {
props: {
rowId: '1',
isSelected: false
}
})
const checkbox = wrapper.find('input[type="checkbox"]')
expect(checkbox.exists()).toBe(true)
expect(checkbox.element).not.toBeChecked()
})
it('renders as checked when isSelected=true', () => {
const wrapper = mount(RowSelectionCheckbox, {
props: {
rowId: '1',
isSelected: true
}
})
const checkbox = wrapper.find('input[type="checkbox"]')
expect(checkbox.element).toBeChecked()
})
it('toggles selection on click', async () => {
const wrapper = mount(RowSelectionCheckbox, {
props: {
rowId: '1',
isSelected: false
}
})
const checkbox = wrapper.find('input[type="checkbox"]')
await checkbox.trigger('click')
expect(wrapper.emitted('toggle')).toBeTruthy()
expect(wrapper.emitted('toggle')[0]).toEqual(['1', true])
})
})Master Checkbox (Select All)
typescript
describe('RowSelectionCheckbox - Master Checkbox', () => {
it('shows master checkbox with select all label', () => {
const wrapper = mount(RowSelectionCheckbox, {
props: {
rowId: 'master',
isSelected: false,
isMaster: true
}
})
const checkbox = wrapper.find('input[type="checkbox"]')
expect(checkbox.exists()).toBe(true)
expect(wrapper.attributes('aria-label')).toContain('Select all')
})
it('shows indeterminate state when partially selected', () => {
const wrapper = mount(RowSelectionCheckbox, {
props: {
rowId: 'master',
isSelected: true,
isMaster: true,
totalRows: 10,
selectedCount: 5 // Partially selected
}
})
const checkbox = wrapper.find('input[type="checkbox"]')
expect(checkbox.element).toHaveProperty('indeterminate', true)
})
it('not indeterminate when all selected', () => {
const wrapper = mount(RowSelectionCheckbox, {
props: {
rowId: 'master',
isSelected: true,
isMaster: true,
totalRows: 10,
selectedCount: 10 // All selected
}
})
const checkbox = wrapper.find('input[type="checkbox"]')
expect(checkbox.element).toHaveProperty('indeterminate', false)
})
it('emits toggle-all event when master clicked', async () => {
const wrapper = mount(RowSelectionCheckbox, {
props: {
rowId: 'master',
isSelected: false,
isMaster: true
}
})
const checkbox = wrapper.find('input[type="checkbox"]')
await checkbox.trigger('click')
expect(wrapper.emitted('toggle-all')).toBeTruthy()
expect(wrapper.emitted('toggle-all')[0]).toEqual([true])
})
})Accessibility
typescript
describe('RowSelectionCheckbox - Accessibility', () => {
it('generates correct ARIA label for row selection', () => {
const wrapper = mount(RowSelectionCheckbox, {
props: {
rowId: 'abc123',
isSelected: false
}
})
const label = wrapper.attributes('aria-label')
expect(label).toContain('abc123')
expect(label).toContain('Select')
})
it('supports keyboard navigation with Space key', async () => {
const wrapper = mount(RowSelectionCheckbox, {
props: {
rowId: '1',
isSelected: false
}
})
const checkbox = wrapper.find('input[type="checkbox"]')
await checkbox.trigger('keydown', { key: ' ' })
expect(wrapper.emitted('toggle')).toBeTruthy()
})
it('supports keyboard navigation with Enter key', async () => {
const wrapper = mount(RowSelectionCheckbox, {
props: {
rowId: '1',
isSelected: false
}
})
const checkbox = wrapper.find('input[type="checkbox"]')
await checkbox.trigger('keydown', { key: 'Enter' })
expect(wrapper.emitted('toggle')).toBeTruthy()
})
it('is focusable via Tab key', () => {
const wrapper = mount(RowSelectionCheckbox, {
props: {
rowId: '1',
isSelected: false
}
})
const checkbox = wrapper.find('input[type="checkbox"]')
expect(checkbox.attributes('tabindex')).not.toBe('-1')
})
})Disabled State
typescript
describe('RowSelectionCheckbox - Disabled State', () => {
it('disables checkbox when disabled=true', () => {
const wrapper = mount(RowSelectionCheckbox, {
props: {
rowId: '1',
isSelected: false,
disabled: true
}
})
const checkbox = wrapper.find('input[type="checkbox"]')
expect(checkbox.attributes('disabled')).toBeDefined()
})
it('does not emit event when disabled and clicked', async () => {
const wrapper = mount(RowSelectionCheckbox, {
props: {
rowId: '1',
isSelected: false,
disabled: true
}
})
const checkbox = wrapper.find('input[type="checkbox"]')
await checkbox.trigger('click')
expect(wrapper.emitted('toggle')).toBeFalsy()
})
it('applies disabled styling', () => {
const wrapper = mount(RowSelectionCheckbox, {
props: {
rowId: '1',
isSelected: false,
disabled: true
}
})
expect(wrapper.classes()).toContain('disabled')
})
})BulkActionsMenu Tests
Complete test suite for bulk actions menu component (22+ tests).
Visibility
typescript
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import BulkActionsMenu from '@/components/Table/BulkActionsMenu.vue'
describe('BulkActionsMenu - Visibility', () => {
it('hides menu when no rows selected', () => {
const wrapper = mount(BulkActionsMenu, {
props: {
selectedCount: 0,
totalRows: 10
}
})
expect(wrapper.find('.bulk-actions-menu').exists()).toBe(false)
})
it('shows menu when rows selected', () => {
const wrapper = mount(BulkActionsMenu, {
props: {
selectedCount: 2,
totalRows: 10
}
})
expect(wrapper.find('.bulk-actions-menu').exists()).toBe(true)
})
it('shows selection counter badge', () => {
const wrapper = mount(BulkActionsMenu, {
props: {
selectedCount: 5,
totalRows: 10
}
})
const counter = wrapper.find('.selection-counter')
expect(counter.exists()).toBe(true)
expect(counter.text()).toContain('5')
})
it('displays "X of Y" format in counter', () => {
const wrapper = mount(BulkActionsMenu, {
props: {
selectedCount: 3,
totalRows: 10
}
})
const counter = wrapper.find('.selection-counter')
expect(counter.text()).toContain('3 of 10')
})
})Actions
typescript
describe('BulkActionsMenu - Actions', () => {
it('emits export event when export clicked', async () => {
const wrapper = mount(BulkActionsMenu, {
props: {
selectedCount: 1,
totalRows: 10
}
})
await wrapper.find('[aria-label*="Export"]').trigger('click')
expect(wrapper.emitted('export')).toBeTruthy()
})
it('emits delete event when delete clicked', async () => {
const wrapper = mount(BulkActionsMenu, {
props: {
selectedCount: 1,
totalRows: 10
}
})
await wrapper.find('[aria-label*="Delete"]').trigger('click')
expect(wrapper.emitted('delete')).toBeTruthy()
})
it('emits copy-ids event when copy IDs clicked', async () => {
const wrapper = mount(BulkActionsMenu, {
props: {
selectedCount: 1,
totalRows: 10
}
})
await wrapper.find('[aria-label*="Copy"]').trigger('click')
expect(wrapper.emitted('copy-ids')).toBeTruthy()
})
it('emits clear-selection event when clear clicked', async () => {
const wrapper = mount(BulkActionsMenu, {
props: {
selectedCount: 5,
totalRows: 10
}
})
await wrapper.find('[aria-label*="Clear"]').trigger('click')
expect(wrapper.emitted('clear-selection')).toBeTruthy()
})
})Disabled State
typescript
describe('BulkActionsMenu - Disabled State', () => {
it('disables all actions when disabled=true', () => {
const wrapper = mount(BulkActionsMenu, {
props: {
selectedCount: 1,
totalRows: 10,
disabled: true
}
})
const buttons = wrapper.findAll('button')
buttons.forEach(button => {
expect(button.attributes('disabled')).toBeDefined()
})
})
it('does not emit events when disabled and clicked', async () => {
const wrapper = mount(BulkActionsMenu, {
props: {
selectedCount: 1,
totalRows: 10,
disabled: true
}
})
await wrapper.find('button').trigger('click')
expect(wrapper.emitted().length).toBe(0)
})
it('shows disabled styling', () => {
const wrapper = mount(BulkActionsMenu, {
props: {
selectedCount: 1,
totalRows: 10,
disabled: true
}
})
expect(wrapper.classes()).toContain('disabled')
})
})ColumnOrderBy Tests
Sorting component test patterns.
typescript
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import ColumnOrderBy from '@/components/Table/ColumnOrderBy.vue'
describe('ColumnOrderBy - Sorting', () => {
it('renders up and down arrow buttons', () => {
const wrapper = mount(ColumnOrderBy, {
props: {
column: 'name',
currentSort: null,
currentDirection: null
}
})
expect(wrapper.find('.up-arrow').exists()).toBe(true)
expect(wrapper.find('.down-arrow').exists()).toBe(true)
})
it('highlights up arrow when ascending', () => {
const wrapper = mount(ColumnOrderBy, {
props: {
column: 'name',
currentSort: 'name',
currentDirection: 'asc'
}
})
const upArrow = wrapper.find('.up-arrow')
expect(upArrow.classes()).toContain('selected')
})
it('highlights down arrow when descending', () => {
const wrapper = mount(ColumnOrderBy, {
props: {
column: 'name',
currentSort: 'name',
currentDirection: 'desc'
}
})
const downArrow = wrapper.find('.down-arrow')
expect(downArrow.classes()).toContain('selected')
})
it('emits sort event with correct direction on arrow click', async () => {
const wrapper = mount(ColumnOrderBy, {
props: {
column: 'name',
currentSort: null,
currentDirection: null
}
})
await wrapper.find('.up-arrow').trigger('click')
expect(wrapper.emitted('sort')).toBeTruthy()
expect(wrapper.emitted('sort')[0][0]).toEqual({
column: 'name',
direction: 'asc'
})
})
it('does not emit when clicking already selected arrow', async () => {
const wrapper = mount(ColumnOrderBy, {
props: {
column: 'name',
currentSort: 'name',
currentDirection: 'asc'
}
})
const selectedArrow = wrapper.find('.up-arrow.selected')
await selectedArrow.trigger('click')
expect(wrapper.emitted('sort')).toBeFalsy()
})
it('handles rapid clicks correctly', async () => {
const wrapper = mount(ColumnOrderBy, {
props: {
column: 'name',
currentSort: null,
currentDirection: null
}
})
const upArrow = wrapper.find('.up-arrow')
// Rapid clicks
await upArrow.trigger('click')
await upArrow.trigger('click')
await upArrow.trigger('click')
expect(wrapper.emitted('sort')).toBeTruthy()
// Should still work despite rapid clicks
})
})DocBitsTable Integration Tests
Complete workflow tests for the main table component.
typescript
describe('DocBitsTable - Complete Workflows', () => {
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' },
{ name: 'name', label: 'Name', field: 'name' }
],
rowKey: 'id',
features: {
selection: true,
bulkActions: true
}
}
})
// Step 1: Select first row
const firstCheckbox = wrapper.find('input[type="checkbox"]')
await firstCheckbox.trigger('click')
expect(wrapper.emitted('selection-change')).toBeTruthy()
const selectionEvent = wrapper.emitted('selection-change')[0][0] as any
expect(selectionEvent.selected.size).toBe(1)
// Step 2: Verify bulk actions menu appears
expect(wrapper.find('.bulk-actions-menu').exists()).toBe(true)
// Step 3: Trigger delete action
const deleteBtn = wrapper.find('[aria-label*="Delete"]')
await deleteBtn.trigger('click')
expect(wrapper.emitted('bulk-action')).toBeTruthy()
const actionEvent = wrapper.emitted('bulk-action')[0][0] as any
expect(actionEvent.action).toBe('delete')
})
it('handles row sorting with ColumnOrderBy', async () => {
const rows = [
{ id: 3, name: 'Charlie' },
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
]
const wrapper = mount(DocBitsTable, {
props: {
rows,
columns: [
{ name: 'id', label: 'ID', field: 'id', sortable: true },
{ name: 'name', label: 'Name', field: 'name', sortable: true }
],
rowKey: 'id',
features: { sorting: true }
}
})
// Click sort on name column
const sortButton = wrapper.find('[aria-label*="Sort"]')
await sortButton.trigger('click')
expect(wrapper.emitted('update:sorting')).toBeTruthy()
})
it('handles pagination state changes', async () => {
const rows = Array.from({ length: 25 }, (_, i) => ({
id: i + 1,
name: `Item ${i + 1}`
}))
const wrapper = mount(DocBitsTable, {
props: {
rows,
columns: [
{ name: 'id', label: 'ID', field: 'id' },
{ name: 'name', label: 'Name', field: 'name' }
],
rowKey: 'id',
pagination: {
page: 1,
rowsPerPage: 10,
rowsNumber: 25
},
features: { pagination: true }
}
})
// Find and click next page button
const nextButton = wrapper.find('[aria-label*="Next"]')
if (nextButton.exists()) {
await nextButton.trigger('click')
expect(wrapper.emitted('update:pagination')).toBeTruthy()
}
})
})Async Operations Tests
Testing components with async data loading.
typescript
describe('DocBitsTable - Async Operations', () => {
it('shows loading state while data fetches', async () => {
const wrapper = mount(DocBitsTable, {
props: {
rows: [],
columns: [{ name: 'id', label: 'ID', field: 'id' }],
loading: false
}
})
// No spinner initially
expect(wrapper.find('.loading-spinner').exists()).toBe(false)
// Update to loading
await wrapper.setProps({ loading: true })
expect(wrapper.find('.loading-spinner').exists()).toBe(true)
// Data arrives
await wrapper.setProps({
loading: false,
rows: [{ id: 1 }]
})
expect(wrapper.find('.loading-spinner').exists()).toBe(false)
expect(wrapper.find('tbody tr').exists()).toBe(true)
})
it('handles async data updates correctly', 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)
// Simulate async data update
await wrapper.setProps({ rows: updatedRows })
expect(wrapper.findAll('tbody tr')).toHaveLength(2)
})
})Running These Examples
Copy any of these test suites into your component's .spec.ts file:
bash
# Run the tests
npm run test
# Run with coverage
npm run test:coverage
# Run in watch mode
npm run test:watch
# Run specific test file
npm run test -- RowSelectionCheckbox.spec.tsBest Practices Applied
All examples follow these patterns:
- Arrange-Act-Assert - Setup, execute, verify
- Descriptive Names - Test names clearly state what is tested
- Single Responsibility - Each test verifies one behavior
- Mock When Needed - Async operations mocked, not real API calls
- Accessibility First - Tests use semantic selectors and ARIA attributes
- User Perspective - Tests verify user-facing behavior
More Examples
- Unit Testing Guide - Detailed patterns
- Component Testing Guide - Integration patterns
- Vitest Docs - Official documentation