Skip to content

Testing: Implement essential unit tests for core functionality #12

@smartlabsAT

Description

@smartlabsAT

🧪 Testing Implementation

Description

Implement a pragmatic testing strategy focusing on critical functionality that provides the most value. We need tests that catch real bugs, not tests for the sake of coverage metrics.

Testing Philosophy

  • Test behavior, not implementation details
  • Focus on critical data operations
  • Prioritize areas prone to regression
  • Test edge cases and error handling
  • Keep tests maintainable and fast

Phase 1: Critical Core Tests (High Priority)

1. Data Integrity Tests

Why: Prevent data corruption and loss (relates to Issue #11)

// tests/utils/fieldSupport.test.ts
describe('Field Support Detection', () => {
  test('should correctly identify supported field types', () => {
    expect(isFieldEditable({ type: 'string' })).toBe(true);
    expect(isFieldEditable({ type: 'geometry' })).toBe(false);
  });
  
  test('should respect readonly fields', () => {
    expect(isFieldEditable({ 
      type: 'string', 
      meta: { readonly: true } 
    })).toBe(false);
  });
  
  test('should handle unknown field types safely', () => {
    expect(isFieldEditable({ type: 'custom_unknown' })).toBe(false);
  });
});

2. API Operations Tests

Why: Core CRUD operations must work reliably

// tests/composables/api.test.ts
describe('API Operations', () => {
  test('should correctly format update payload', () => {
    const payload = formatUpdatePayload(field, value);
    expect(payload).toHaveProperty(field.key);
    expect(payload[field.key]).toBe(value);
  });
  
  test('should handle null values correctly', () => {
    const payload = formatUpdatePayload(field, null);
    expect(payload[field.key]).toBeNull();
  });
  
  test('should handle batch updates', async () => {
    const updates = await batchUpdate(items, changes);
    expect(updates).toHaveLength(items.length);
  });
});

3. Translation Field Tests

Why: Complex logic prone to bugs (Issues #8)

// tests/composables/useLanguageSelector.test.ts
describe('Language Selection', () => {
  test('should detect existing languages for field', () => {
    const fields = [
      { key: 'title:en-US' },
      { key: 'title:de-DE' }
    ];
    const existing = getExistingLanguages('title', fields);
    expect(existing).toContain('en-US');
    expect(existing).toContain('de-DE');
    expect(existing).not.toContain('fr-FR');
  });
  
  test('should filter out already selected languages', () => {
    const available = getAvailableLanguages('title', fields);
    expect(available).not.toContain('en-US');
    expect(available).toContain('fr-FR');
  });
});

4. Filter & Search Tests

Why: Complex search logic with type detection

// tests/utils/search.test.ts
describe('Search Type Detection', () => {
  test('should detect UUID search', () => {
    const uuid = '123e4567-e89b-12d3-a456-426614174000';
    expect(getSearchType(uuid)).toBe('uuid');
  });
  
  test('should detect numeric search', () => {
    expect(getSearchType('123')).toBe('numeric');
    expect(getSearchType('abc')).toBe('text');
  });
  
  test('should build correct filter for each type', () => {
    const textFilter = buildSearchFilter('text', 'test');
    expect(textFilter._icontains).toBe('test');
    
    const uuidFilter = buildSearchFilter('uuid', uuid);
    expect(uuidFilter._eq).toBe(uuid);
  });
});

Phase 2: UI Component Tests (Medium Priority)

5. Cell Renderer Tests

Why: Ensure data displays correctly

// tests/components/CellRenderers.test.ts
describe('Cell Renderers', () => {
  test('ImageCell should handle missing images gracefully', () => {
    const wrapper = mount(ImageCell, {
      props: { value: null }
    });
    expect(wrapper.find('.placeholder').exists()).toBe(true);
  });
  
  test('StatusCell should apply correct color class', () => {
    const wrapper = mount(StatusCell, {
      props: { value: 'active', status: 'success' }
    });
    expect(wrapper.classes()).toContain('status-success');
  });
});

6. Inline Editor Tests

Why: Critical for data entry (Issues #5, #9, #10)

// tests/components/InlineEditPopover.test.ts
describe('Inline Edit Popover', () => {
  test('should open for supported fields', async () => {
    const field = { type: 'string', key: 'title' };
    const wrapper = mount(InlineEditPopover, {
      props: { field, value: 'test' }
    });
    await wrapper.trigger('click');
    expect(wrapper.find('.editor').exists()).toBe(true);
  });
  
  test('should not open for unsupported fields', async () => {
    const field = { type: 'geometry', key: 'location' };
    const wrapper = mount(InlineEditPopover, {
      props: { field, value: {} }
    });
    await wrapper.trigger('click');
    expect(wrapper.find('.editor').exists()).toBe(false);
    expect(wrapper.emitted('warning')).toBeTruthy();
  });
});

Phase 3: Integration Tests (Lower Priority)

7. Pagination Tests

// tests/composables/useTablePagination.test.ts
describe('Pagination', () => {
  test('should calculate correct page ranges', () => {
    const { startIndex, endIndex } = getPageRange(3, 25);
    expect(startIndex).toBe(50);
    expect(endIndex).toBe(75);
  });
});

8. Sort Tests

// tests/composables/useTableSort.test.ts
describe('Table Sorting', () => {
  test('should toggle sort direction', () => {
    const sort = { by: 'name', desc: false };
    const newSort = toggleSort(sort, 'name');
    expect(newSort.desc).toBe(true);
  });
});

Test Infrastructure

Setup Required

// package.json
{
  "scripts": {
    "test": "vitest",
    "test:watch": "vitest --watch",
    "test:coverage": "vitest --coverage",
    "test:ui": "vitest --ui"
  },
  "devDependencies": {
    "@vue/test-utils": "^2.4.0",
    "@vitest/ui": "^1.0.0",
    "vitest": "^1.0.0",
    "happy-dom": "^12.0.0",
    "@testing-library/vue": "^8.0.0"
  }
}

Test Configuration

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

export default defineConfig({
  plugins: [vue()],
  test: {
    environment: 'happy-dom',
    coverage: {
      reporter: ['text', 'json', 'html'],
      exclude: ['node_modules/', 'tests/']
    }
  }
});

Testing Guidelines

What TO Test

Business logic (calculations, transformations)
Data mutations (ensure data isn't accidentally modified)
Edge cases (null, undefined, empty arrays)
Error handling (API failures, invalid inputs)
Complex conditions (multi-language detection, field support)

What NOT to Test

Directus internals (trust the framework)
Simple getters/setters (no logic)
CSS/Styling (visual regression tests later)
Third-party libraries (they have their own tests)
Trivial computed properties (unless complex)

Success Metrics

  • Zero data corruption bugs in production
  • 80% reduction in regression bugs
  • Tests run in < 30 seconds
  • New features include tests
  • CI/CD pipeline with test gates

Implementation Priority

  1. Week 1: Field support & API tests (prevent data loss)
  2. Week 2: Translation & search tests (complex logic)
  3. Week 3: Component tests (user interactions)
  4. Week 4: Integration tests & CI setup

GitHub Actions Integration

# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v3
      - name: Install dependencies
        run: pnpm install
      - name: Run tests
        run: pnpm test
      - name: Upload coverage
        uses: codecov/codecov-action@v3

Note: This testing strategy focuses on preventing real bugs rather than achieving coverage metrics. Start with Phase 1 as these tests provide the most value for effort invested.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions