# Testing Guide for Sonik Component Library

## Test Modes Overview

Sonik uses two different test modes optimized for different types of code:

### Unit Tests (Node Mode) - Fast ⚡
- **For**: Composables, utilities, helper functions, non-UI logic
- **Speed**: Very fast (no browser overhead)
- **Config**: `vitest.unit.config.ts`
- **Pattern**: `lib/composables/**/*.spec.ts`, `lib/utils/**/*.spec.ts`

### Component Tests (Browser Mode) - Visual 🎨
- **For**: Vue components, UI interactions, visual regression
- **Speed**: Slower (requires browser instance)
- **Config**: `vitest.config.ts`
- **Pattern**: `lib/components/**/*.spec.ts`

**Rule of thumb**: If it doesn't need a DOM or visual testing, use unit tests!

## Testing Strategy by Component Type

### Pass-through Components
For components that wrap PrimeVue without customization:
- **Don't retest PrimeVue functionality** - Focus only on Sonik integration
- **Verify theme integration** - Test "renders with LoopAdmin theme" 
- **Visual regression only** - Ensure correct styling application

### Customized/Extended Components  
For components with custom behavior or significant modifications:
- **Functional tests** - Component-specific behavior and interactions
- **Accessibility tests** - WCAG AA compliance using axe-core
- **Visual regression** - Screenshots for all variants and states
- **Theme integration** - Verify LoopAdmin theme application

### Interaction Testing
- **Prefer page-driven interactions** - Use `page.getByRole()`, `page.click()` via browser mode
- **Avoid wrapper interactions** - Minimize direct Vue Test Utils interactions where possible

## Visual Regression Testing

### How to Take Screenshots
```typescript
// Element-specific screenshot
await expect(page.getByRole('button', { name: 'My Button' })).toMatchScreenshot('button-default.png')

// Container with multiple elements  
await expect(page.getByTestId('button-variants')).toMatchScreenshot('button-all-variants.png')
```

### Screenshot Configuration
Fixed viewport (1280x720) in `vitest.config.ts` eliminates dimension variations:
```typescript
browser: {
  provider: playwright(),
  instances: [{ browser: 'chromium', viewport: { width: 1280, height: 720 } }],
}
```

## Test Commands

### Run All Tests
```bash
npm test                         # Run both unit and component tests in parallel
npm run test:ci                  # Run all tests once (for CI)
npm run test:coverage            # Run all with coverage
```

### Unit Tests Only (Fast)
```bash
npm run test:unit                # Watch mode
npm run test:unit:run            # Single run
npm run test:unit:coverage       # With coverage
```

### Component Tests Only (Browser)
```bash
npm run test:component                      # Watch mode
npm run test:component:run                  # Single run
npm run test:component:ui                   # Visual UI mode
npm run test:component:coverage             # With coverage
npm run test:component:update-snapshots     # Update screenshots
```

### CI Screenshot Updates
```bash
./scripts/update-ci-screenshots.sh  # Generate Linux baselines for CI
```

## Test File Structure

```
lib/components/Button/
  Button.spec.ts              # Colocated test file
  __screenshots__/            # PNG baselines (darwin + linux)
tests/
  setup.ts                    # Global setup
  component-utils.ts          # Mount utilities  
  accessibility.ts            # A11y helpers
```

## Component Testing Utilities

```typescript
// Mount with LoopAdmin theme (default)
const wrapper = mountSonikComponent(Component, { props: { ... } })

// Mount with specific theme
const wrapper = mountSonikComponentWithTheme(Component, 'LoopAdmin', { props: { ... } })

// Always unmount when done
wrapper.unmount()
```

## How to Write Tests

### Pass-through Component Example
```typescript
it('renders with LoopAdmin theme', async () => {
  const wrapper = mountSonikComponentWithTheme(Button, 'LoopAdmin', {
    props: { label: 'Themed Button', severity: 'primary' }
  })
  
  await expect(page.getByRole('button')).toMatchScreenshot('button-loopadmin-theme.png')
  wrapper.unmount()
})
```

### Custom Component Example  
```typescript
it('handles custom behavior correctly', async () => {
  const wrapper = mountSonikComponent(CustomComponent, {
    props: { customProp: 'value' }
  })
  
  // Functional test
  expect(wrapper.exists()).toBe(true)
  
  // Page interaction (preferred)
  await page.getByRole('button').click()
  await expect(page.getByText('Expected Result')).toBeVisible()
  
  // Visual regression
  await expect(page.getByTestId('custom-component')).toMatchScreenshot('custom-behavior.png')
  
  // Accessibility  
  await expectComponentAccessible('button', { level: 'AA' })
  
  wrapper.unmount()
})
```

## Screenshot Management

### Update Local Screenshots
```bash
npm run test:update-snapshots -- lib/components/Button/Button.spec.ts
```

### Update CI Screenshots
```bash
./scripts/update-ci-screenshots.sh  # Generates Linux baselines via Docker
```

Both macOS (`-darwin.png`) and Linux (`-linux.png`) baselines are required.

## Writing Unit Tests (Composables/Utils)

### Example: Testing a Composable
```typescript
// lib/composables/useCounter.spec.ts
import { describe, it, expect } from 'vitest'
import { useCounter } from './useCounter'

describe('useCounter', () => {
  it('should increment the count', () => {
    const { count, increment } = useCounter()
    increment()
    expect(count.value).toBe(1)
  })

  it('should not increment beyond max', () => {
    const { count, increment } = useCounter({ initialValue: 9, max: 10 })
    increment()
    expect(count.value).toBe(10)
    increment() // Should not go beyond 10
    expect(count.value).toBe(10)
  })
})
```

**When to use unit tests:**
- ✅ Composables (useCounter, useDebounce, etc.)
- ✅ Utility functions (formatDate, parseQuery, etc.)
- ✅ Pure logic without DOM interaction
- ✅ Type guards and validators
- ❌ Vue components (use component tests instead)

## Accessibility Testing

### How to Test Accessibility
```typescript
import { expectComponentAccessible } from '../../accessibility'

// Test WCAG AA compliance  
await expectComponentAccessible('button', { 
  level: 'AA',
  excludeRules: ['page-has-heading-one'] // Not relevant for components
})
```

### Common Exclusions
- `page-has-heading-one` - Not applicable to isolated components
- `color-contrast` - May need exclusion during development

## Troubleshooting

### Screenshot Issues
- **Differences**: Usually theme changes - fixed viewport _should_ eliminate random variations
- **Cross-platform**: Maintain both `-darwin.png` and `-linux.png` baselines

### Running Specific Test Suites
```bash
# Run only unit tests for composables
npm run test:unit -- lib/composables

# Run only component tests for Button
npm run test:component:run -- lib/components/Button
```

### Debug Commands
```bash
npm run test:ui                    # UI interface
npm test -- Button.spec.ts        # Single file  
npm test -- --run -u Button.spec.ts   # Update specific screenshots
```

## Component Testing Checklist

### Pass-through Components
- [ ] Theme integration test (`renders with LoopAdmin theme`)
- [ ] Visual regression for main variants

### Custom Components  
- [ ] Functional behavior tests
- [ ] WCAG AA accessibility compliance
- [ ] Visual regression for all states
- [ ] Keyboard navigation (if applicable)
- [ ] Theme compatibility