Skip to content

Unit Testing

Guide to unit testing DocBits components with Vitest.

Overview

All DocBits components are thoroughly tested with:

  • 100+ unit tests covering all functionality
  • 40+ component tests for integration scenarios
  • 30+ test files with comprehensive coverage
  • Full edge case handling documented in tests

Testing Setup

Installation

bash
npm install -D vitest @vue/test-utils jsdom

Configuration

typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  test: {
    globals: true,
    environment: 'jsdom',
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html']
    }
  }
})

Testing Pattern: Props Validation

Test that components accept and validate props correctly:

typescript
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import ColumnOrderBy from '@/components/Table/ColumnOrderBy.vue'

describe('ColumnOrderBy - Props Validation', () => {
  it('accepts required props', () => {
    const wrapper = mount(ColumnOrderBy, {
      props: {
        column: 'name',
        currentSort: null,
        currentDirection: null
      }
    })

    expect(wrapper.exists()).toBe(true)
  })

  it('handles string column prop', () => {
    const wrapper = mount(ColumnOrderBy, {
      props: {
        column: 'id',
        currentSort: null,
        currentDirection: null
      }
    })

    expect(wrapper.props('column')).toBe('id')
  })

  it('handles null currentSort', () => {
    const wrapper = mount(ColumnOrderBy, {
      props: {
        column: 'name',
        currentSort: null,
        currentDirection: null
      }
    })

    expect(wrapper.props('currentSort')).toBeNull()
  })
})

Testing Pattern: Event Emission

Test that components emit correct events with proper payloads:

typescript
describe('ColumnOrderBy - Event Emission', () => {
  it('emits sort event on arrow click', async () => {
    const wrapper = mount(ColumnOrderBy, {
      props: {
        column: 'name',
        currentSort: null,
        currentDirection: null
      }
    })

    const upArrow = wrapper.find('.up-arrow')
    await upArrow.trigger('click')

    expect(wrapper.emitted('sort')).toBeTruthy()
    expect(wrapper.emitted('sort')[0]).toEqual([
      { column: 'name', direction: 'asc' }
    ])
  })

  it('emits with correct direction', async () => {
    const wrapper = mount(ColumnOrderBy, {
      props: {
        column: 'name',
        currentSort: null,
        currentDirection: null
      }
    })

    const downArrow = wrapper.find('.down-arrow')
    await downArrow.trigger('click')

    const emitted = wrapper.emitted('sort')[0][0] as any
    expect(emitted.direction).toBe('desc')
  })
})

Testing Pattern: User Interactions

Test component behavior with user inputs:

typescript
describe('RowSelectionCheckbox - User Interaction', () => {
  it('toggles selection on click', async () => {
    const wrapper = mount(RowSelectionCheckbox, {
      props: {
        rowId: '1',
        isSelected: false
      }
    })

    const checkbox = wrapper.find('input')
    await checkbox.trigger('click')

    expect(wrapper.emitted('toggle')).toBeTruthy()
    expect(wrapper.emitted('toggle')[0]).toEqual(['1', true])
  })

  it('shows as checked when isSelected=true', () => {
    const wrapper = mount(RowSelectionCheckbox, {
      props: {
        rowId: '1',
        isSelected: true
      }
    })

    const checkbox = wrapper.find('input')
    expect(checkbox.element).toBeChecked()
  })

  it('shows as unchecked when isSelected=false', () => {
    const wrapper = mount(RowSelectionCheckbox, {
      props: {
        rowId: '1',
        isSelected: false
      }
    })

    const checkbox = wrapper.find('input')
    expect(checkbox.element).not.toBeChecked()
  })
})

Testing Pattern: Computed Properties

Test computed properties and reactive state:

typescript
describe('RowSelectionCheckbox - Computed Properties', () => {
  it('shows indeterminate state correctly', () => {
    const wrapper = mount(RowSelectionCheckbox, {
      props: {
        rowId: 'master',
        isMaster: true,
        isSelected: true,
        totalRows: 10,
        selectedCount: 5  // Partially selected
      }
    })

    const checkbox = wrapper.find('input')
    expect(checkbox.element).toHaveProperty('indeterminate', true)
  })

  it('generates correct ARIA label', () => {
    const wrapper = mount(RowSelectionCheckbox, {
      props: {
        rowId: 'abc123',
        isSelected: false
      }
    })

    expect(wrapper.attributes('aria-label')).toContain('abc123')
  })
})

Testing Pattern: Edge Cases

Test boundary conditions and unusual inputs:

typescript
describe('ColumnOrderBy - Edge Cases', () => {
  it('handles empty column name', () => {
    const wrapper = mount(ColumnOrderBy, {
      props: {
        column: '',
        currentSort: null,
        currentDirection: null
      }
    })

    expect(wrapper.exists()).toBe(true)
  })

  it('handles rapid clicks', async () => {
    const wrapper = mount(ColumnOrderBy, {
      props: {
        column: 'name',
        currentSort: null,
        currentDirection: null
      }
    })

    const arrow = wrapper.find('.up-arrow')

    // Rapid clicks
    await arrow.trigger('click')
    await arrow.trigger('click')
    await arrow.trigger('click')

    // Should only emit when changing state
    expect(wrapper.emitted('sort')).toBeTruthy()
  })

  it('handles same arrow click twice', async () => {
    const wrapper = mount(ColumnOrderBy, {
      props: {
        column: 'name',
        currentSort: 'name',
        currentDirection: 'asc'
      }
    })

    const sameArrow = wrapper.find('.up-arrow.selected')
    await sameArrow.trigger('click')

    // Should not emit when clicking already selected arrow
    expect(wrapper.emitted('sort')).toBeFalsy()
  })
})

Running Tests

Run All Tests

bash
npm run test

Run Tests with Coverage

bash
npm run test:coverage

Run Tests in Watch Mode

bash
npm run test:watch

Run Specific Test File

bash
npm run test -- src/components/Table/ColumnOrderBy.spec.ts

Run Tests Matching Pattern

bash
npm run test -- --grep "ColumnOrderBy"

Coverage Requirements

All DocBits components maintain:

  • Line Coverage: >95%
  • Branch Coverage: >90%
  • Function Coverage: >95%

Best Practices

1. Test User Behavior, Not Implementation

typescript
// ✅ Good: Tests what user sees
it('shows sorted arrow in blue', () => {
  const wrapper = mount(ColumnOrderBy, {
    props: {
      column: 'name',
      currentSort: 'name',
      currentDirection: 'asc'
    }
  })
  const arrow = wrapper.find('.up-arrow.selected')
  expect(arrow.classes()).toContain('selected')
})

// ❌ Bad: Tests internal implementation
it('sets selected property to true', () => {
  const wrapper = mount(ColumnOrderBy, {
    props: { ... }
  })
  expect(wrapper.vm.selectedArrow === 'asc').toBe(true)
})

2. Test Edge Cases

Always test boundary conditions:

typescript
// Empty data
const emptyRows = []

// Single item
const singleRow = [{ id: 1 }]

// Large dataset
const largeDataset = Array(10000).fill().map((_, i) => ({ id: i }))

// Special characters
const specialChars = { name: '<script>alert("xss")</script>' }

3. Use Descriptive Test Names

typescript
// ✅ Good: Clear description
it('should emit sort event with correct direction when down arrow clicked', () => { ... })

// ❌ Bad: Vague description
it('should work correctly', () => { ... })

4. Arrange-Act-Assert Pattern

typescript
it('handles row selection', () => {
  // Arrange: Set up the component
  const wrapper = mount(RowSelectionCheckbox, {
    props: { rowId: '1', isSelected: false }
  })

  // Act: Perform user action
  const checkbox = wrapper.find('input')
  await checkbox.trigger('click')

  // Assert: Verify result
  expect(wrapper.emitted('toggle')).toBeTruthy()
})

Debugging Tests

View Component HTML

typescript
it('renders correctly', () => {
  const wrapper = mount(Component, { props: { ... } })
  console.log(wrapper.html())  // Print rendered HTML
})

Check Event Emissions

typescript
it('emits events', async () => {
  const wrapper = mount(Component, { props: { ... } })
  await wrapper.find('button').trigger('click')
  console.log(wrapper.emitted())  // Print all emitted events
})

Watch Component Updates

typescript
it('updates state', async () => {
  const wrapper = mount(Component, { props: { ... } })
  console.log(wrapper.vm.$data)  // Component state before

  await wrapper.find('button').trigger('click')

  console.log(wrapper.vm.$data)  // Component state after
})

DocBits Component Library