Skip to content

Unit tests ​

Overview ​

Unit tests isolate and verify individual components and composables in isolation. In Vue.js with Vitest:

  • Vitest: Lightning-fast unit test runner compatible with Vite
  • Vue Test Utils: Mount Vue components in a test environment, interact with them, and assert behavior
  • Focus: Test component logic (props, emits, state, lifecycle) and composable hooks without DOM rendering
  • Isolation: Mock external dependencies (API calls, third-party libraries) to test only the code you wrote

Run tests with:

bash
pnpm test:unit

Components ​

VOvChart.test.ts
ts
import { describe, it, expect, vi, beforeAll } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import type { ChartData } from 'chart.js'
import VOvChart from '../VOvChart.vue'

// Mock ResizeObserver globally for all tests
beforeAll(() => {
  global.ResizeObserver = vi.fn().mockImplementation(() => ({
    observe: vi.fn(),
    disconnect: vi.fn(),
  })) as never
})

const mockBarChartData: ChartData<'bar'> = {
  labels: ['January', 'February', 'March'],
  datasets: [
    {
      label: 'Sales',
      data: [65, 59, 80],
      backgroundColor: 'rgba(255, 99, 132, 0.5)',
    },
  ],
}

const mockLineChartData: ChartData<'line'> = {
  labels: ['Jan', 'Feb', 'Mar', 'Apr'],
  datasets: [
    {
      label: 'Dataset 1',
      data: [10, 20, 30, 40],
      borderColor: 'rgb(75, 192, 192)',
    },
  ],
}

const mockPieChartData: ChartData<'pie'> = {
  labels: ['Red', 'Blue', 'Yellow'],
  datasets: [
    {
      label: 'Votes',
      data: [12, 19, 3],
      backgroundColor: ['red', 'blue', 'yellow'],
    },
  ],
}

// Helper to provide chart component stubs for all tests
const chartStubs = {
  Bar: true,
  Line: true,
  Pie: true,
  Doughnut: true,
  Radar: true,
  PolarArea: true,
  Scatter: true,
}

describe('VOvChart', () => {
  describe('Rendering', () => {
    it('mounts without error when bar prop is true', () => {
      const wrapper = mount(VOvChart, {
        props: {
          bar: true,
          chartData: mockBarChartData,
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('mounts without error when line prop is true', () => {
      const wrapper = mount(VOvChart, {
        props: {
          line: true,
          chartData: mockLineChartData,
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('mounts without error when pie prop is true', () => {
      const wrapper = mount(VOvChart, {
        props: {
          pie: true,
          chartData: mockPieChartData,
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('mounts without error when doughnut prop is true', () => {
      const wrapper = mount(VOvChart, {
        props: {
          doughnut: true,
          chartData: mockPieChartData,
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('mounts without error when radar prop is true', () => {
      const wrapper = mount(VOvChart, {
        props: {
          radar: true,
          chartData: mockLineChartData,
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('mounts without error when polarArea prop is true', () => {
      const wrapper = mount(VOvChart, {
        props: {
          polarArea: true,
          chartData: mockLineChartData,
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('mounts without error when scatter prop is true', () => {
      const wrapper = mount(VOvChart, {
        props: {
          scatter: true,
          chartData: mockLineChartData,
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('renders nothing when no chart type is specified', () => {
      const wrapper = mount(VOvChart, {
        props: {
          chartData: mockBarChartData,
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('renders only first chart when multiple types are specified', () => {
      const wrapper = mount(VOvChart, {
        props: {
          bar: true,
          line: true,
          chartData: mockBarChartData,
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })
  })

  describe('Data Props', () => {
    it('accepts chart data prop', () => {
      const wrapper = mount(VOvChart, {
        props: {
          bar: true,
          chartData: mockBarChartData,
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.props('chartData')).toEqual(mockBarChartData)
    })

    it('handles empty datasets', () => {
      const emptyData: ChartData<'bar'> = {
        labels: ['test'],
        datasets: [],
      }
      const wrapper = mount(VOvChart, {
        props: {
          bar: true,
          chartData: emptyData,
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.props('chartData')).toEqual(emptyData)
    })

    it('handles line chart data', () => {
      const wrapper = mount(VOvChart, {
        props: {
          line: true,
          chartData: mockLineChartData,
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.props('chartData')).toEqual(mockLineChartData)
    })

    it('handles pie chart data', () => {
      const wrapper = mount(VOvChart, {
        props: {
          pie: true,
          chartData: mockPieChartData,
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.props('chartData')).toEqual(mockPieChartData)
    })
  })

  describe('Options Props', () => {
    it('accepts custom chart options', () => {
      const customOptions = {
        responsive: true,
        maintainAspectRatio: false,
      }
      const wrapper = mount(VOvChart, {
        props: {
          bar: true,
          chartData: mockBarChartData,
          chartOptions: customOptions,
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.props('chartOptions')).toEqual(customOptions)
    })

    it('defaults to empty options object', () => {
      const wrapper = mount(VOvChart, {
        props: {
          bar: true,
          chartData: mockBarChartData,
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.props('chartOptions')).toEqual({})
    })
  })

  describe('Plugins Props', () => {
    it('accepts plugin array prop', () => {
      const mockPlugin = { id: 'test-plugin' }
      const wrapper = mount(VOvChart, {
        props: {
          bar: true,
          chartData: mockBarChartData,
          chartPlugins: [mockPlugin],
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.props('chartPlugins')).toEqual([mockPlugin])
    })

    it('defaults to empty plugins array', () => {
      const wrapper = mount(VOvChart, {
        props: {
          bar: true,
          chartData: mockBarChartData,
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.props('chartPlugins')).toEqual([])
    })
  })

  describe('Click Handling', () => {
    it('accepts clickable prop as true', () => {
      const wrapper = mount(VOvChart, {
        props: {
          bar: true,
          clickable: true,
          chartData: mockBarChartData,
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.props('clickable')).toBe(true)
    })

    it('accepts clickable prop as false', () => {
      const wrapper = mount(VOvChart, {
        props: {
          bar: true,
          clickable: false,
          chartData: mockBarChartData,
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.props('clickable')).toBe(false)
    })

    it('defaults clickable to false', () => {
      const wrapper = mount(VOvChart, {
        props: {
          bar: true,
          chartData: mockBarChartData,
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.props('clickable')).toBe(false)
    })
  })

  describe('Chart Type Selection', () => {
    it('mounts correctly when bar is true', () => {
      const wrapper = mount(VOvChart, {
        props: { bar: true, chartData: mockBarChartData },
        global: { stubs: chartStubs },
      })
      expect(wrapper.exists()).toBe(true)
    })

    it('mounts correctly when line is true', () => {
      const wrapper = mount(VOvChart, {
        props: { line: true, chartData: mockLineChartData },
        global: { stubs: chartStubs },
      })
      expect(wrapper.exists()).toBe(true)
    })

    it('mounts correctly when pie is true', () => {
      const wrapper = mount(VOvChart, {
        props: { pie: true, chartData: mockPieChartData },
        global: { stubs: chartStubs },
      })
      expect(wrapper.exists()).toBe(true)
    })

    it('mounts correctly when doughnut is true', () => {
      const wrapper = mount(VOvChart, {
        props: { doughnut: true, chartData: mockPieChartData },
        global: { stubs: chartStubs },
      })
      expect(wrapper.exists()).toBe(true)
    })

    it('mounts correctly when radar is true', () => {
      const wrapper = mount(VOvChart, {
        props: { radar: true, chartData: mockLineChartData },
        global: { stubs: chartStubs },
      })
      expect(wrapper.exists()).toBe(true)
    })

    it('mounts correctly when polarArea is true', () => {
      const wrapper = mount(VOvChart, {
        props: { polarArea: true, chartData: mockLineChartData },
        global: { stubs: chartStubs },
      })
      expect(wrapper.exists()).toBe(true)
    })

    it('mounts correctly when scatter is true', () => {
      const wrapper = mount(VOvChart, {
        props: { scatter: true, chartData: mockLineChartData },
        global: { stubs: chartStubs },
      })
      expect(wrapper.exists()).toBe(true)
    })
  })

  describe('ResizeObserver', () => {
    it('mounts with ResizeObserver mock available', () => {
      const wrapper = mount(VOvChart, {
        props: {
          bar: true,
          chartData: mockBarChartData,
        },
        global: { stubs: chartStubs },
      })
      expect(global.ResizeObserver).toBeDefined()
      expect(wrapper.vm).toBeDefined()
    })

    it('can be unmounted without errors', async () => {
      const wrapper = mount(VOvChart, {
        props: {
          bar: true,
          chartData: mockBarChartData,
        },
        global: { stubs: chartStubs },
      })
      await wrapper.unmount()
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Edge Cases', () => {
    it('handles no chart type props', () => {
      const wrapper = mount(VOvChart, {
        props: {
          chartData: mockBarChartData,
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles multiple chart types (first wins)', () => {
      const wrapper = mount(VOvChart, {
        props: {
          bar: true,
          line: true,
          pie: true,
          chartData: mockBarChartData,
        },
        global: { stubs: chartStubs },
      })
      expect(wrapper.props('bar')).toBe(true)
      expect(wrapper.props('line')).toBe(true)
      expect(wrapper.props('pie')).toBe(true)
    })

    it('handles prop changes without errors', async () => {
      const wrapper = mount(VOvChart, {
        props: {
          bar: true,
          chartData: mockBarChartData,
        },
        global: { stubs: chartStubs },
      })
      await wrapper.setProps({ line: true, bar: false })
      expect(wrapper.props('line')).toBe(true)
      expect(wrapper.props('bar')).toBe(false)
      await flushPromises()
    })
  })

  describe('All Chart Types Integration', () => {
    it('renders all 7 chart types without errors', async () => {
      const wrappers = [
        mount(VOvChart, {
          props: { bar: true, chartData: mockBarChartData },
          global: { stubs: chartStubs },
        }),
        mount(VOvChart, {
          props: { line: true, chartData: mockLineChartData },
          global: { stubs: chartStubs },
        }),
        mount(VOvChart, {
          props: { pie: true, chartData: mockPieChartData },
          global: { stubs: chartStubs },
        }),
        mount(VOvChart, {
          props: { doughnut: true, chartData: mockPieChartData },
          global: { stubs: chartStubs },
        }),
        mount(VOvChart, {
          props: { radar: true, chartData: mockLineChartData },
          global: { stubs: chartStubs },
        }),
        mount(VOvChart, {
          props: { polarArea: true, chartData: mockLineChartData },
          global: { stubs: chartStubs },
        }),
        mount(VOvChart, {
          props: { scatter: true, chartData: mockLineChartData },
          global: { stubs: chartStubs },
        }),
      ]
      wrappers.forEach((wrapper) => {
        expect(wrapper.exists()).toBe(true)
      })
    })
  })
})
VOvDialog.test.ts
ts
import { describe, it, expect, vi } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import type { OvAction } from '../index'
import VOvDialog from '../VOvDialog.vue'

// Mock visualViewport for Vuetify VOverlay with proper event handling
if (typeof global.visualViewport === 'undefined') {
  const mockViewport: Partial<VisualViewport> & {
    addEventListener: ReturnType<typeof vi.fn>
    removeEventListener: ReturnType<typeof vi.fn>
  } = {
    width: 1024,
    height: 768,
    offsetLeft: 0,
    offsetTop: 0,
    pageLeft: 0,
    pageTop: 0,
    scale: 1,
    addEventListener: vi.fn(),
    removeEventListener: vi.fn(),
  }
  global.visualViewport = mockViewport as VisualViewport
}

// Helper to open dialog and wait for render
async function openDialog(wrapper: ReturnType<typeof mount>) {
  await wrapper.setProps({ modelValue: true })
  await wrapper.vm.$nextTick()
  await flushPromises()
}

describe('VOvDialog', () => {
  describe('Rendering', () => {
    it('mounts without error', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Test Dialog',
        },
      })
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('renders VDialog component', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Test Dialog',
        },
      })
      const dialog = wrapper.findComponent({ name: 'VDialog' })
      expect(dialog.exists()).toBe(true)
    })

    it('renders VCard component inside dialog', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Test Dialog',
        },
      })
      await openDialog(wrapper)
      const card = wrapper.findComponent({ name: 'VCard' })
      expect(card.exists()).toBe(true)
    })

    it('renders VCardText component for content', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Test Dialog',
          content: 'Test content',
        },
      })
      await openDialog(wrapper)
      const cardText = wrapper.findComponent({ name: 'VCardText' })
      expect(cardText.exists()).toBe(true)
    })

    it('renders VCardActions component for buttons', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Test Dialog',
          actions: ['ok'],
        },
      })
      await openDialog(wrapper)
      const cardActions = wrapper.findComponent({ name: 'VCardActions' })
      expect(cardActions.exists()).toBe(true)
    })
  })

  describe('Dialog Properties', () => {
    it('applies title prop to card', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'My Dialog Title',
        },
      })
      await openDialog(wrapper)
      const card = wrapper.findComponent({ name: 'VCard' })
      expect(card.props('title')).toBe('My Dialog Title')
    })

    it('applies subtitle prop to card', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Title',
          subtitle: 'Subtitle text',
        },
      })
      await openDialog(wrapper)
      const card = wrapper.findComponent({ name: 'VCard' })
      expect(card.props('subtitle')).toBe('Subtitle text')
    })

    it('applies icon prop to card', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          icon: '$mdiAlert',
        },
      })
      await openDialog(wrapper)
      const card = wrapper.findComponent({ name: 'VCard' })
      expect(card.props('prependIcon')).toBe('$mdiAlert')
    })

    it('applies color prop to card', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          color: 'primary',
        },
      })
      await openDialog(wrapper)
      const card = wrapper.findComponent({ name: 'VCard' })
      expect(card.props('color')).toBe('primary')
    })

    it('applies persistent prop to dialog', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          persistent: true,
        },
      })
      const dialog = wrapper.findComponent({ name: 'VDialog' })
      expect(dialog.props('persistent')).toBe(true)
    })

    it('applies fullscreen prop to dialog', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          fullscreen: true,
        },
      })
      const dialog = wrapper.findComponent({ name: 'VDialog' })
      expect(dialog.props('fullscreen')).toBe(true)
    })

    it('applies scrollable prop to dialog', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          scrollable: true,
        },
      })
      const dialog = wrapper.findComponent({ name: 'VDialog' })
      expect(dialog.props('scrollable')).toBe(true)
    })

    it('defaults persistent to false', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
        },
      })
      const dialog = wrapper.findComponent({ name: 'VDialog' })
      expect(dialog.props('persistent')).toBe(false)
    })

    it('defaults fullscreen to false', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
        },
      })
      const dialog = wrapper.findComponent({ name: 'VDialog' })
      expect(dialog.props('fullscreen')).toBe(false)
    })

    it('defaults scrollable to false', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
        },
      })
      const dialog = wrapper.findComponent({ name: 'VDialog' })
      expect(dialog.props('scrollable')).toBe(false)
    })
  })

  describe('Content Props', () => {
    it('renders content as text when no format is provided', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          content: 'Simple text content',
        },
      })
      await openDialog(wrapper)
      const cardText = wrapper.findComponent({ name: 'VCardText' })
      expect(cardText.text()).toContain('Simple text content')
    })

    it('renders content without chip when hasContentProps is false', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          content: 'Content',
        },
      })
      const chips = wrapper.findAllComponents({ name: 'VChip' })
      expect(chips.length).toBe(0)
    })

    it('renders content as chip when format is provided', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          content: 'Formatted content',
          contentFormat: { color: 'primary' },
        },
      })
      await openDialog(wrapper)
      const chip = wrapper.findComponent({ name: 'VChip' })
      expect(chip.exists()).toBe(true)
    })

    it('handles empty content prop', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles undefined content prop', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          content: undefined,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('accepts multiple content formats as array', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          content: 'Multi-format content',
          contentFormat: [{ color: 'primary' }, { variant: 'outlined' }],
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('accepts single content format object', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          content: 'Single format content',
          contentFormat: { color: 'error', variant: 'elevated' },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Actions Props', () => {
    it('renders action buttons when actions prop is provided as array', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          actions: ['ok', 'cancel'],
        },
      })
      await openDialog(wrapper)
      const buttons = wrapper.findAllComponents({ name: 'VBtn' })
      expect(buttons.length).toBeGreaterThanOrEqual(2)
    })

    it('renders single action button when actions prop is string', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          actions: 'ok',
        },
      })
      await openDialog(wrapper)
      const buttons = wrapper.findAllComponents({ name: 'VBtn' })
      expect(buttons.length).toBeGreaterThanOrEqual(1)
    })

    it('renders no action buttons when actions prop is empty array', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          actions: [],
        },
      })
      const buttons = wrapper.findAllComponents({ name: 'VBtn' })
      expect(buttons.length).toBe(0)
    })

    it('renders no action buttons when actions prop is undefined', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
        },
      })
      const buttons = wrapper.findAllComponents({ name: 'VBtn' })
      expect(buttons.length).toBe(0)
    })

    it('renders multiple action buttons with different names', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          actions: ['save', 'cancel', 'delete'],
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles action objects with format properties', () => {
      const actions: OvAction[] = [
        {
          name: 'confirm',
          format: { color: 'success', icon: '$mdiCheck' },
        },
        {
          name: 'cancel',
          format: { color: 'error', icon: '$mdiClose' },
        },
      ]
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          actions: actions,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('accepts multiple action formats as array', () => {
      const actions: OvAction[] = [
        {
          name: 'action1',
          format: [{ color: 'primary' }, { variant: 'elevated' }],
        },
      ]
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          actions: actions,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('accepts single action format as object', () => {
      const actions: OvAction[] = [
        {
          name: 'action1',
          format: { color: 'primary', variant: 'outlined' },
        },
      ]
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          actions: actions,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Exposed Dialog State', () => {
    it('exposes dialog ref that can be accessed', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          modelValue: false,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('dialog ref is initially false', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          modelValue: false,
        },
      })
      expect(wrapper.props('modelValue')).toBe(false)
    })

    it('dialog ref can be toggled', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          modelValue: false,
        },
      })
      await wrapper.setProps({ modelValue: true })
      await wrapper.vm.$nextTick()
      expect(wrapper.props('modelValue')).toBe(true)
    })

    it('dialog ref can be opened from false to true', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          modelValue: false,
        },
      })
      expect(wrapper.props('modelValue')).toBe(false)
      await wrapper.setProps({ modelValue: true })
      await wrapper.vm.$nextTick()
      expect(wrapper.props('modelValue')).toBe(true)
    })

    it('dialog ref can be closed from true to false', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          modelValue: true,
        },
      })
      await wrapper.vm.$nextTick()
      expect(wrapper.props('modelValue')).toBe(true)
      await wrapper.setProps({ modelValue: false })
      await wrapper.vm.$nextTick()
      expect(wrapper.props('modelValue')).toBe(false)
    })
  })

  describe('Event Emissions', () => {
    it('emits action event when custom action is clicked', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          actions: ['custom'],
        },
      })
      await wrapper.vm.$nextTick()
      const customAction: OvAction = 'custom'
      wrapper.vm.$emit('action', customAction)
      expect(wrapper.emitted('action')).toBeTruthy()
    })

    it('emits submit event when submit action is triggered', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          actions: ['submit'],
          actionSubmit: 'submit',
        },
      })
      await wrapper.vm.$nextTick()
      const submitAction: OvAction = 'submit'
      wrapper.vm.$emit('submit', submitAction)
      expect(wrapper.emitted('submit')).toBeTruthy()
    })

    it('emits cancel event when cancel action is triggered', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          actions: ['cancel'],
          actionCancel: 'cancel',
        },
      })
      await wrapper.vm.$nextTick()
      wrapper.vm.$emit('cancel')
      expect(wrapper.emitted('cancel')).toBeTruthy()
    })

    it('emits multiple events for different actions', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          actions: ['action1', 'action2'],
        },
      })
      await wrapper.vm.$nextTick()
      wrapper.vm.$emit('action', 'action1')
      wrapper.vm.$emit('action', 'action2')
      expect(wrapper.emitted('action')).toBeTruthy()
      expect(wrapper.emitted('action')?.length).toBe(2)
    })
  })

  describe('Slots', () => {
    it('renders content slot when provided', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
        },
        slots: {
          content: { template: '<div class="custom-content">Custom Content</div>' },
        },
      })
      await openDialog(wrapper)
      // Verify component mounts with slot defined
      expect(wrapper.vm).toBeDefined()
    })

    it('renders actions slot when provided', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
        },
        slots: {
          actions: { template: '<button class="custom-button">Custom Button</button>' },
        },
      })
      await openDialog(wrapper)
      // Verify component mounts with slot defined
      expect(wrapper.vm).toBeDefined()
    })

    it('content slot receives onClose callback', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
        },
        slots: {
          content: '<div class="content"></div>',
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('both slots can be used together', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
        },
        slots: {
          content: { template: '<div class="content">Content Slot</div>' },
          actions: { template: '<div class="actions">Actions Slot</div>' },
        },
      })
      await openDialog(wrapper)
      // Verify component mounts with both slots defined
      expect(wrapper.vm).toBeDefined()
    })

    it('content slot overrides content prop', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          content: 'Prop content',
        },
        slots: {
          content: { template: '<div class="slot-override">Slot content</div>' },
        },
      })
      await openDialog(wrapper)
      // Verify component mounts with slot defined (slot takes precedence over prop)
      expect(wrapper.vm).toBeDefined()
    })

    it('actions slot works independently of actions prop', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          actions: ['ok'],
        },
        slots: {
          actions: '<button>Extra Action</button>',
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Complex Scenarios', () => {
    it('renders complete dialog with title, content, icon, and actions', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Confirmation',
          subtitle: 'Are you sure?',
          icon: '$mdiAlert',
          color: 'warning',
          content: 'This action cannot be undone.',
          actions: ['cancel', 'confirm'],
          actionCancel: 'cancel',
          actionSubmit: 'confirm',
        },
      })
      await openDialog(wrapper)
      const card = wrapper.findComponent({ name: 'VCard' })
      expect(card.props('title')).toBe('Confirmation')
      expect(card.props('subtitle')).toBe('Are you sure?')
      expect(card.props('prependIcon')).toBe('$mdiAlert')
      expect(card.props('color')).toBe('warning')
    })

    it('renders dialog with formatted content and actions', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Styled Dialog',
          content: 'Important message',
          contentFormat: { color: 'error', variant: 'elevated' },
          actions: [
            { name: 'accept', format: { color: 'success', icon: '$mdiCheck' } },
            { name: 'reject', format: { color: 'error', icon: '$mdiClose' } },
          ],
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('renders fullscreen persistent dialog', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Fullscreen Dialog',
          fullscreen: true,
          persistent: true,
          content: 'Important content',
          actions: ['close'],
        },
      })
      const dialog = wrapper.findComponent({ name: 'VDialog' })
      expect(dialog.props('fullscreen')).toBe(true)
      expect(dialog.props('persistent')).toBe(true)
    })

    it('renders scrollable dialog with long content', () => {
      const longContent = 'Lorem ipsum dolor sit amet. '.repeat(50) + 'Lorem ipsum dolor sit amet.'
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Long Content Dialog',
          scrollable: true,
          content: longContent,
          actions: ['close'],
        },
      })
      const dialog = wrapper.findComponent({ name: 'VDialog' })
      expect(dialog.props('scrollable')).toBe(true)
    })

    it('renders dialog with mixed action types (strings and objects)', () => {
      const actions: OvAction[] = [
        'cancel',
        { name: 'save', format: { color: 'primary' } },
        'delete',
        { name: 'archive', format: { color: 'info' } },
      ]
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Mixed Actions Dialog',
          actions: actions,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('renders dialog with multiple content formats', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          content: 'Content',
          contentFormat: [
            { color: 'primary', rules: { type: 'required', params: true } },
            { variant: 'outlined' },
          ],
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('renders dialog with multiple action formats', () => {
      const actions: OvAction[] = [
        {
          name: 'action1',
          format: [{ color: 'primary' }, { variant: 'elevated', size: 'large' }],
        },
      ]
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          actions: actions,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Props Updates', () => {
    it('updates dialog title when title prop changes', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Initial Title',
        },
      })
      await openDialog(wrapper)
      await wrapper.setProps({ title: 'Updated Title' })
      const card = wrapper.findComponent({ name: 'VCard' })
      expect(card.props('title')).toBe('Updated Title')
    })

    it('updates dialog content when content prop changes', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          content: 'Initial content',
        },
      })
      await wrapper.setProps({ content: 'Updated content' })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('updates actions when actions prop changes', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          actions: ['action1'],
        },
      })
      await wrapper.setProps({ actions: ['action1', 'action2', 'action3'] })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('updates dialog state when persistent prop changes', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          persistent: false,
        },
      })
      const dialog = wrapper.findComponent({ name: 'VDialog' })
      expect(dialog.props('persistent')).toBe(false)
      await wrapper.setProps({ persistent: true })
      expect(dialog.props('persistent')).toBe(true)
    })

    it('updates multiple props simultaneously', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Initial',
          content: 'Initial content',
          icon: '$mdiInformation',
          color: 'info',
        },
      })
      await openDialog(wrapper)
      await wrapper.setProps({
        title: 'Updated',
        content: 'Updated content',
        icon: '$mdiAlert',
        color: 'warning',
      })
      const card = wrapper.findComponent({ name: 'VCard' })
      expect(card.props('title')).toBe('Updated')
      expect(card.props('color')).toBe('warning')
    })
  })

  describe('Edge Cases', () => {
    it('handles empty title prop', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: '',
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles undefined title prop', () => {
      const wrapper = mount(VOvDialog, {
        props: {},
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles very long title', () => {
      const longTitle = 'A'.repeat(200)
      const wrapper = mount(VOvDialog, {
        props: {
          title: longTitle,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles special characters in title', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog with <special> & "characters"',
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles all undefined optional props', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          persistent: undefined,
          fullscreen: undefined,
          scrollable: undefined,
          subtitle: undefined,
          icon: undefined,
          color: undefined,
          content: undefined,
          contentFormat: undefined,
          actions: undefined,
          actionFormat: undefined,
          actionSubmit: undefined,
          actionCancel: undefined,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles dialog mounting and unmounting', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
        },
      })
      expect(wrapper.exists()).toBe(true)
      await wrapper.unmount()
      expect(wrapper.vm).toBeDefined()
    })

    it('handles rapid prop changes', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog 1',
        },
      })
      for (let i = 2; i <= 10; i++) {
        await wrapper.setProps({ title: `Dialog ${i}` })
      }
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('handles empty actions array and then adding actions', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          actions: [],
        },
      })
      const buttons = wrapper.findAllComponents({ name: 'VBtn' })
      expect(buttons.length).toBe(0)

      await wrapper.setProps({ actions: ['ok', 'cancel'] })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Breakpoint and Width Handling', () => {
    it('calculates width based on display breakpoint', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
        },
      })
      const dialog = wrapper.findComponent({ name: 'VDialog' })
      expect(dialog.props('width')).toBeDefined()
    })

    it('width prop is a computed value based on breakpoint', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Content and Action Formatting', () => {
    it('applies OvFormat to content correctly', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          content: 'Formatted content',
          contentFormat: { color: 'primary', icon: '$mdiInformation' },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies OvActionFormat to actions correctly', () => {
      const actions: OvAction[] = [
        {
          name: 'save',
          format: { color: 'success', text: 'Save Changes' },
        },
      ]
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          actions: actions,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('uses action name as button text when format text is not provided', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          actions: ['save'],
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('uses format text when provided instead of action name', () => {
      const actions: OvAction[] = [
        {
          name: 'save',
          format: { text: 'Save Changes' },
        },
      ]
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          actions: actions,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Action Button Rendering', () => {
    it('renders all action buttons', async () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          actions: ['action1', 'action2', 'action3'],
        },
      })
      await openDialog(wrapper)
      const buttons = wrapper.findAllComponents({ name: 'VBtn' })
      expect(buttons.length).toBeGreaterThanOrEqual(3)
    })

    it('renders buttons with correct props from actionFormat', () => {
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          actions: ['ok'],
          actionFormat: { color: 'primary' },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('renders buttons with individual format properties', () => {
      const actions: OvAction[] = [
        { name: 'ok', format: { color: 'success' } },
        { name: 'cancel', format: { color: 'error' } },
      ]
      const wrapper = mount(VOvDialog, {
        props: {
          title: 'Dialog',
          actions: actions,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })
})
VOvEditor.test.ts
ts
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'

// very light mocks so we don't pull in real TipTap/Vuetify internals
vi.mock('@tiptap/vue-3', () => ({
  useEditor: vi.fn(() => ({
    value: {
      getHTML: vi.fn(() => '<p>from editor</p>'),
      destroy: vi.fn(),
      isActive: vi.fn(() => false),
      chain: vi.fn(() => ({
        focus: vi.fn(() => ({
          toggleBold: vi.fn(() => ({ run: vi.fn() })),
          toggleItalic: vi.fn(() => ({ run: vi.fn() })),
          toggleUnderline: vi.fn(() => ({ run: vi.fn() })),
          toggleStrike: vi.fn(() => ({ run: vi.fn() })),
          toggleBulletList: vi.fn(() => ({ run: vi.fn() })),
          toggleOrderedList: vi.fn(() => ({ run: vi.fn() })),
          toggleHeading: vi.fn(() => ({ run: vi.fn() })),
        })),
      })),
    },
  })),
  EditorContent: { name: 'EditorContent', template: '<div class="editor-content" />' },
}))

vi.mock('@tiptap/starter-kit', () => ({ default: {} }))

import VOvEditor from '../VOvEditor.vue'

describe('VOvEditor', () => {
  it('mounts with v-model and toolbar like sandbox example', () => {
    const wrapper = mount(VOvEditor, {
      props: {
        modelValue: '<p>Initial</p>',
        toolbar: [
          'bold',
          'italic',
          'underline',
          'strike',
          'bulletList',
          'orderedList',
          'heading1',
          'heading2',
          'heading3',
        ],
        toolbarClass: 'mb-4',
      },
    })

    expect(wrapper.exists()).toBe(true)
    expect(wrapper.props('modelValue')).toBe('<p>Initial</p>')
    expect(wrapper.props('toolbar')).toHaveLength(9)
    expect(wrapper.props('toolbarClass')).toBe('mb-4')
  })

  it('emits updated when content changes', async () => {
    const wrapper = mount(VOvEditor, {
      props: {
        modelValue: '<p>Initial</p>',
      },
    })

    // The 'updated' event should be emitted when model value changes
    await wrapper.setProps({ modelValue: '<p>Updated</p>' })
    await wrapper.vm.$nextTick()

    // The component should be mounted and functional
    expect(wrapper.exists()).toBe(true)
  })

  it('can be unmounted without errors', async () => {
    const wrapper = mount(VOvEditor, {
      props: {
        modelValue: '',
      },
    })

    await wrapper.unmount()
    expect(wrapper).toBeDefined()
  })

  it('toggles bold formatting when bold button is clicked', async () => {
    const wrapper = mount(VOvEditor, {
      props: {
        modelValue: '<p>Test</p>',
        toolbar: ['bold'],
      },
    })

    const buttons = wrapper.findAllComponents({ name: 'VBtn' })
    expect(buttons.length).toBeGreaterThan(0)

    // Click the bold button
    await buttons[0]!.trigger('click')
    await wrapper.vm.$nextTick()

    expect(wrapper.exists()).toBe(true)
  })
})
VOvForm.test.ts
ts
import { describe, it, expect, vi } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import type { OvFormOptions, OvFormData } from '../index'
import VOvForm from '../VOvForm.vue'

// Mock visualViewport for Vuetify VOverlay with proper event handling
if (typeof global.visualViewport === 'undefined') {
  const mockViewport: Partial<VisualViewport> & {
    addEventListener: ReturnType<typeof vi.fn>
    removeEventListener: ReturnType<typeof vi.fn>
  } = {
    width: 1024,
    height: 768,
    offsetLeft: 0,
    offsetTop: 0,
    pageLeft: 0,
    pageTop: 0,
    scale: 1,
    addEventListener: vi.fn(),
    removeEventListener: vi.fn(),
  }
  global.visualViewport = mockViewport as VisualViewport
}

// Mock Vuetify components and composables
vi.mock('vuetify/components', () => ({
  VTextField: { name: 'VTextField', template: '<input />' },
  VSelect: { name: 'VSelect', template: '<select></select>' },
  VCombobox: { name: 'VCombobox', template: '<input />' },
  VAutocomplete: { name: 'VAutocomplete', template: '<input />' },
  VFileInput: { name: 'VFileInput', template: '<input type="file" />' },
  VSwitch: { name: 'VSwitch', template: '<input type="checkbox" />' },
  VCheckbox: { name: 'VCheckbox', template: '<input type="checkbox" />' },
  VRating: { name: 'VRating', template: '<div></div>' },
  VTextarea: { name: 'VTextarea', template: '<textarea></textarea>' },
}))

describe('VOvForm', () => {
  const basicFormOptions: OvFormOptions = {
    fields: [
      {
        type: 'text',
        name: 'username',
        label: 'Username',
        placeholder: 'Enter username',
      },
      {
        type: 'email',
        name: 'email',
        label: 'Email',
        placeholder: 'Enter email',
      },
    ],
    actions: ['submit', 'cancel'],
    actionSubmit: 'submit',
    actionCancel: 'cancel',
  }

  describe('Rendering', () => {
    it('mounts without error', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
        },
      })
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('renders VContainer component', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
        },
      })
      const container = wrapper.findComponent({ name: 'VContainer' })
      expect(container.exists()).toBe(true)
    })

    it('renders VForm component', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
        },
      })
      const form = wrapper.findComponent({ name: 'VForm' })
      expect(form.exists()).toBe(true)
    })

    it('renders form fields based on options', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
        },
      })
      const fields = wrapper.findAllComponents({ name: 'VTextField' })
      expect(fields.length).toBeGreaterThan(0)
    })

    it('renders action buttons when actions are provided', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
        },
      })
      const buttons = wrapper.findAllComponents({ name: 'VBtn' })
      expect(buttons.length).toBeGreaterThan(0)
    })

    it('renders form with loading state false by default', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
          loading: false,
        },
      })
      expect(wrapper.props('loading')).toBe(false)
    })
  })

  describe('Props - Form Options', () => {
    it('accepts options prop with fields and actions', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
        },
      })
      expect(wrapper.props('options')).toEqual(basicFormOptions)
    })

    it('renders all field types from options', () => {
      const allFieldsOptions: OvFormOptions = {
        fields: [
          { type: 'text', name: 'text', label: 'Text' },
          { type: 'email', name: 'email', label: 'Email' },
          { type: 'password', name: 'password', label: 'Password' },
          { type: 'number', name: 'number', label: 'Number' },
          { type: 'date', name: 'date', label: 'Date' },
          { type: 'time', name: 'time', label: 'Time' },
          { type: 'datetime', name: 'datetime', label: 'DateTime' },
          { type: 'textarea', name: 'textarea', label: 'Textarea' },
          { type: 'checkbox', name: 'checkbox', label: 'Checkbox' },
          { type: 'switch', name: 'switch', label: 'Switch' },
          { type: 'rating', name: 'rating', label: 'Rating' },
          { type: 'select', name: 'select', label: 'Select', items: ['A', 'B'] },
          { type: 'combobox', name: 'combobox', label: 'Combobox', items: ['A', 'B'] },
          { type: 'autocomplete', name: 'autocomplete', label: 'Autocomplete', items: ['A', 'B'] },
          { type: 'file', name: 'file', label: 'File' },
        ],
      }
      const wrapper = mount(VOvForm, {
        props: {
          options: allFieldsOptions,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('hides fields marked with hidden property', () => {
      const optionsWithHidden: OvFormOptions = {
        fields: [
          { type: 'text', name: 'visible', label: 'Visible' },
          { type: 'text', name: 'hidden', label: 'Hidden', hidden: true },
        ],
      }
      const wrapper = mount(VOvForm, {
        props: {
          options: optionsWithHidden,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Props - Data', () => {
    it('accepts data prop and initializes form values', async () => {
      const initialData: OvFormData = {
        username: 'testuser',
        email: 'test@example.com',
      }
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
          data: initialData,
        },
      })
      await wrapper.vm.$nextTick()
      expect(wrapper.props('data')).toEqual(initialData)
    })

    it('handles undefined data prop', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
        },
      })
      expect(wrapper.props('data')).toBeUndefined()
    })

    it('updates form when data prop changes', async () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
          data: { username: 'user1', email: 'user1@example.com' },
        },
      })
      await wrapper.vm.$nextTick()

      await wrapper.setProps({
        data: { username: 'user2', email: 'user2@example.com' },
      })
      await flushPromises()
      expect(wrapper.props('data')?.username).toBe('user2')
    })
  })

  describe('Props - Translation Function', () => {
    it('accepts translation function prop', () => {
      const mockT = (text?: string) => `translated_${text}`
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
          t: mockT,
        },
      })
      expect(wrapper.props('t')).toBe(mockT)
    })

    it('uses translation function for labels', () => {
      const mockT = (text?: string) => `translated_${text}`
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
          t: mockT,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('defaults translation function if not provided', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
        },
      })
      const defaultT = wrapper.props('t')
      expect(typeof defaultT).toBe('function')
    })
  })

  describe('Props - Loading', () => {
    it('accepts loading prop as false', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
          loading: false,
        },
      })
      expect(wrapper.props('loading')).toBe(false)
    })

    it('defaults loading to false', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
        },
      })
      expect(wrapper.props('loading')).toBe(false)
    })
  })

  describe('Form Field Rendering', () => {
    it('renders text field', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'text', name: 'text', label: 'Text' }],
          },
        },
      })
      const textFields = wrapper.findAllComponents({ name: 'VTextField' })
      expect(textFields.length).toBeGreaterThan(0)
    })

    it('renders email field as VTextField with email type', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'email', name: 'email', label: 'Email' }],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('renders password field with visibility toggle', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'password', name: 'password', label: 'Password' }],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('renders textarea field', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'textarea', name: 'textarea', label: 'Textarea', rows: 5 }],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('renders select field with items', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              {
                type: 'select',
                name: 'select',
                label: 'Select',
                items: ['Option A', 'Option B'],
              },
            ],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('renders checkbox field', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'checkbox', name: 'checkbox', label: 'Checkbox' }],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('renders switch field', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'switch', name: 'switch', label: 'Switch' }],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('renders rating field', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'rating', name: 'rating', label: 'Rating', length: 5 }],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Form Actions', () => {
    it('renders action buttons', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [],
            actions: ['submit', 'cancel'],
          },
        },
      })
      const buttons = wrapper.findAllComponents({ name: 'VBtn' })
      expect(buttons.length).toBeGreaterThan(0)
    })

    it('renders no action buttons when actions array is empty', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [],
            actions: [],
          },
        },
      })
      const buttons = wrapper.findAllComponents({ name: 'VBtn' })
      expect(buttons.length).toBe(0)
    })

    it('renders no action buttons when actions is undefined', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [],
          },
        },
      })
      const buttons = wrapper.findAllComponents({ name: 'VBtn' })
      expect(buttons.length).toBe(0)
    })

    it('renders custom action buttons', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [],
            actions: ['submit', 'reset', { name: 'custom', format: { color: 'red' } }],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Form Submission', () => {
    it('emits submit event with form data on submit action', async () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              {
                type: 'text',
                name: 'username',
                label: 'Username',
                rules: [],
              },
            ],
            actions: ['submit'],
            actionSubmit: 'submit',
          },
          data: { username: 'testuser' },
        },
      })
      await wrapper.vm.$nextTick()

      // Simulate form validation and submit
      // In real scenario, this would be triggered by button click
      expect(wrapper.vm).toBeDefined()
    })

    it('emits cancel event on cancel action', async () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [],
            actions: ['cancel'],
            actionCancel: 'cancel',
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('emits reset event on reset action', async () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'text', name: 'username', label: 'Username', value: 'default' }],
            actions: ['reset'],
            actionReset: 'reset',
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('emits validate event on validate action', async () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [],
            actions: ['validate'],
            actionValidate: 'validate',
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('emits action event on custom action', async () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [],
            actions: [{ name: 'customAction', format: {} }],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Form Validation', () => {
    it('applies validation rules to fields', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              {
                type: 'email',
                name: 'email',
                label: 'Email',
                rules: [{ type: 'email', params: true, message: 'Invalid email' }],
              },
            ],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('displays error messages from options.errors', async () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'text', name: 'username', label: 'Username' }],
            errors: [{ name: 'username', message: 'Username is required' }],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('updates error display when errors prop changes', async () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'text', name: 'username', label: 'Username' }],
            errors: [],
          },
        },
      })
      await wrapper.setProps({
        options: {
          ...basicFormOptions,
          errors: [{ name: 'username', message: 'Error' }],
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Form State Management', () => {
    it('maintains form state with field values', async () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
          data: { username: 'user1', email: 'user1@example.com' },
        },
      })
      await wrapper.vm.$nextTick()
      expect(wrapper.vm).toBeDefined()
    })

    it('applies default values from field definitions', () => {
      const optionsWithDefaults: OvFormOptions = {
        fields: [
          { type: 'text', name: 'field1', label: 'Field 1', value: 'default1' },
          { type: 'text', name: 'field2', label: 'Field 2', value: 'default2' },
        ],
      }
      const wrapper = mount(VOvForm, {
        props: {
          options: optionsWithDefaults,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('merges provided data with default field values', async () => {
      const optionsWithDefaults: OvFormOptions = {
        fields: [
          { type: 'text', name: 'field1', label: 'Field 1', value: 'default1' },
          { type: 'text', name: 'field2', label: 'Field 2', value: 'default2' },
        ],
      }
      const wrapper = mount(VOvForm, {
        props: {
          options: optionsWithDefaults,
          data: { field1: 'custom1' },
        },
      })
      await wrapper.vm.$nextTick()
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Form Layout', () => {
    it('renders columns based on cols option', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              { type: 'text', name: 'field1', label: 'Field 1' },
              { type: 'text', name: 'field2', label: 'Field 2' },
              { type: 'text', name: 'field3', label: 'Field 3' },
            ],
            cols: 3,
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('defaults to 1 column layout when cols is not specified', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              { type: 'text', name: 'field1', label: 'Field 1' },
              { type: 'text', name: 'field2', label: 'Field 2' },
            ],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('aligns action buttons according to actionAlign option', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [],
            actions: ['submit', 'cancel'],
            actionAlign: 'center',
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('supports left action alignment', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [],
            actions: ['submit'],
            actionAlign: 'left',
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('supports right action alignment', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [],
            actions: ['submit'],
            actionAlign: 'right',
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Form Properties', () => {
    it('applies disabled state to form when disabled option is true', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'text', name: 'field', label: 'Field' }],
            disabled: true,
          },
        },
      })
      const form = wrapper.findComponent({ name: 'VForm' })
      expect(form.props('disabled')).toBe(true)
    })

    it('applies readonly state to form when readonly option is true', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'text', name: 'field', label: 'Field' }],
            readonly: true,
          },
        },
      })
      const form = wrapper.findComponent({ name: 'VForm' })
      expect(form.props('readonly')).toBe(true)
    })

    it('applies fastFail option to form validation', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'text', name: 'field', label: 'Field' }],
            fastFail: true,
          },
        },
      })
      const form = wrapper.findComponent({ name: 'VForm' })
      expect(form.props('fastFail')).toBe(true)
    })

    it('applies autocomplete option to form', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'text', name: 'field', label: 'Field' }],
            autocomplete: 'off',
          },
        },
      })
      expect(wrapper.props('options').autocomplete).toBe('off')
    })

    it('focuses first field when focusFirst option is true', async () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              { type: 'text', name: 'field1', label: 'Field 1' },
              { type: 'text', name: 'field2', label: 'Field 2' },
            ],
            focusFirst: true,
          },
        },
      })
      await wrapper.vm.$nextTick()
      expect(wrapper.vm).toBeDefined()
    })

    it('does not focus first field when focusFirst is false or undefined', async () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'text', name: 'field', label: 'Field' }],
            focusFirst: false,
          },
        },
      })
      await wrapper.vm.$nextTick()
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Field Properties', () => {
    it('applies label to field', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'text', name: 'field', label: 'Field Label' }],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies placeholder to field', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'text', name: 'field', label: 'Field', placeholder: 'Enter value' }],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies hint to field', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              {
                type: 'text',
                name: 'field',
                label: 'Field',
                hint: 'This is a hint',
              },
            ],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies required property to field', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'text', name: 'field', label: 'Field', required: true }],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies readonly property to field', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'text', name: 'field', label: 'Field', readonly: true }],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies disabled property to field', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'text', name: 'field', label: 'Field', disabled: true }],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies clearable property to field', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'text', name: 'field', label: 'Field', clearable: true }],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies icon properties to field', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              {
                type: 'text',
                name: 'field',
                label: 'Field',
                prependIcon: '$mdiAccount',
                appendIcon: '$mdiClose',
              },
            ],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies variant property to field', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              {
                type: 'text',
                name: 'field',
                label: 'Field',
                variant: 'underlined',
              },
            ],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies density property to field', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'text', name: 'field', label: 'Field', density: 'compact' }],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies color property to field', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'text', name: 'field', label: 'Field', color: 'primary' }],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies counter property to textarea', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              {
                type: 'textarea',
                name: 'field',
                label: 'Field',
                counter: 200,
              },
            ],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies prefix and suffix to text fields', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              {
                type: 'text',
                name: 'price',
                label: 'Price',
                prefix: '$',
                suffix: 'USD',
              },
            ],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Textarea Field Properties', () => {
    it('applies rows property to textarea', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              {
                type: 'textarea',
                name: 'textarea',
                label: 'Textarea',
                rows: 8,
              },
            ],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies noResize property to textarea', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              {
                type: 'textarea',
                name: 'textarea',
                label: 'Textarea',
                noResize: true,
              },
            ],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies autoGrow property to textarea', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              {
                type: 'textarea',
                name: 'textarea',
                label: 'Textarea',
                autoGrow: true,
              },
            ],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Rating Field Properties', () => {
    it('applies length property to rating', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              {
                type: 'rating',
                name: 'rating',
                label: 'Rating',
                length: 10,
              },
            ],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies size property to rating', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              {
                type: 'rating',
                name: 'rating',
                label: 'Rating',
                size: 32,
              },
            ],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies itemLabels property to rating', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              {
                type: 'rating',
                name: 'rating',
                label: 'Rating',
                itemLabels: ['Poor', 'Fair', 'Good', 'Very Good', 'Excellent'],
              },
            ],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('disables rating when form is disabled', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'rating', name: 'rating', label: 'Rating' }],
            disabled: true,
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Selection Field Properties', () => {
    it('applies items property to select field', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              {
                type: 'select',
                name: 'select',
                label: 'Select',
                items: ['Option A', 'Option B', 'Option C'],
              },
            ],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies chips property to select field', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              {
                type: 'select',
                name: 'select',
                label: 'Select',
                items: ['A', 'B'],
                chips: true,
              },
            ],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies multiple property to select field', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              {
                type: 'select',
                name: 'select',
                label: 'Select',
                items: ['A', 'B'],
                multiple: true,
              },
            ],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles combobox field type', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              {
                type: 'combobox',
                name: 'combobox',
                label: 'Combobox',
                items: ['A', 'B'],
              },
            ],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles autocomplete field type', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              {
                type: 'autocomplete',
                name: 'autocomplete',
                label: 'Autocomplete',
                items: ['A', 'B'],
              },
            ],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Password Field', () => {
    it('renders password field with password type', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'password', name: 'password', label: 'Password' }],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('toggles password visibility with eye icon', async () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'password', name: 'password', label: 'Password' }],
          },
        },
      })
      await wrapper.vm.$nextTick()
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Responsive Layout', () => {
    it('adapts layout for mobile devices', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              { type: 'text', name: 'field1', label: 'Field 1' },
              { type: 'text', name: 'field2', label: 'Field 2' },
            ],
            cols: 2,
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Mounting and Unmounting', () => {
    it('mounts and initializes correctly', async () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('can be unmounted without errors', async () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
        },
      })
      await wrapper.unmount()
      expect(wrapper).toBeDefined()
    })
  })

  describe('Props Updates', () => {
    it('updates form when options prop changes', async () => {
      const initialOptions: OvFormOptions = {
        fields: [{ type: 'text', name: 'field1', label: 'Field 1' }],
      }
      const wrapper = mount(VOvForm, {
        props: {
          options: initialOptions,
        },
      })

      const newOptions: OvFormOptions = {
        fields: [
          { type: 'text', name: 'field1', label: 'Field 1' },
          { type: 'text', name: 'field2', label: 'Field 2' },
        ],
      }
      await wrapper.setProps({ options: newOptions })
      await flushPromises()
      expect(wrapper.props('options').fields.length).toBe(2)
    })

    it('updates loading state', async () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
          loading: false,
        },
      })
      expect(wrapper.props('loading')).toBe(false)
    })
  })

  describe('Complex Form Scenarios', () => {
    it('handles form with all field types like sandbox-form example', async () => {
      const complexFormOptions: OvFormOptions = {
        fields: [
          { type: 'text', name: 'text1', label: 'Text 1' },
          { type: 'email', name: 'email', label: 'Email' },
          { type: 'number', name: 'number', label: 'Number' },
          { type: 'password', name: 'password', label: 'Password' },
          { type: 'textarea', name: 'textarea', label: 'Textarea' },
          { type: 'switch', name: 'acceptTerms', label: 'Accept Terms' },
          { type: 'rating', name: 'rating', label: 'Rating' },
          { type: 'checkbox', name: 'checkbox', label: 'Checkbox' },
          {
            type: 'select',
            name: 'select',
            label: 'Select',
            items: ['A', 'B', 'C'],
            multiple: true,
          },
          {
            type: 'combobox',
            name: 'combobox',
            label: 'Combobox',
            items: ['A', 'B', 'C'],
          },
          {
            type: 'autocomplete',
            name: 'autocomplete',
            label: 'Autocomplete',
            items: ['A', 'B', 'C'],
          },
          { type: 'file', name: 'file', label: 'File' },
          { type: 'date', name: 'date', label: 'Date' },
          { type: 'time', name: 'time', label: 'Time' },
          { type: 'datetime', name: 'datetime', label: 'DateTime' },
        ],
        cols: 3,
        actions: ['validate', 'cancel', 'submit', 'reset'],
        actionFormat: { color: 'primary' },
        actionAlign: 'center',
        actionSubmit: 'submit',
        actionReset: 'reset',
        actionValidate: 'validate',
        actionCancel: 'cancel',
      }

      const wrapper = mount(VOvForm, {
        props: {
          options: complexFormOptions,
          data: {
            text1: 'Test',
            email: 'test@example.com',
            number: 42,
          },
        },
      })

      await wrapper.vm.$nextTick()
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('handles form with validation rules', () => {
      const formWithValidation: OvFormOptions = {
        fields: [
          {
            type: 'text',
            name: 'username',
            label: 'Username',
            rules: [
              { type: 'required', params: true, message: 'Required' },
              {
                type: 'min-length',
                params: 3,
                message: 'Min 3 chars',
              },
            ],
          },
          {
            type: 'email',
            name: 'email',
            label: 'Email',
            rules: [
              { type: 'required', params: true, message: 'Required' },
              { type: 'email', params: true, message: 'Invalid email' },
            ],
          },
        ],
        actions: ['submit'],
        actionSubmit: 'submit',
      }

      const wrapper = mount(VOvForm, {
        props: {
          options: formWithValidation,
        },
      })

      expect(wrapper.vm).toBeDefined()
    })

    it('handles form with field errors from server', () => {
      const formWithErrors: OvFormOptions = {
        fields: [
          { type: 'text', name: 'username', label: 'Username' },
          { type: 'email', name: 'email', label: 'Email' },
        ],
        errors: [
          { name: 'username', message: 'Username already taken' },
          { name: 'email', message: 'Email already registered' },
        ],
      }

      const wrapper = mount(VOvForm, {
        props: {
          options: formWithErrors,
        },
      })

      expect(wrapper.vm).toBeDefined()
    })

    it('handles form with custom actions', () => {
      const formWithCustomActions: OvFormOptions = {
        fields: [{ type: 'text', name: 'field', label: 'Field' }],
        actions: [
          'submit',
          'reset',
          {
            name: 'customAction',
            format: { color: 'red', text: 'Custom' },
          },
          {
            name: 'anotherAction',
            format: { color: 'blue', icon: '$mdiCheck' },
          },
        ],
      }

      const wrapper = mount(VOvForm, {
        props: {
          options: formWithCustomActions,
        },
      })

      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Edge Cases', () => {
    it('handles empty form with no fields', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles form with only hidden fields', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [
              { type: 'text', name: 'field1', label: 'Field 1', hidden: true },
              { type: 'text', name: 'field2', label: 'Field 2', hidden: true },
            ],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles form with special characters in labels', () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'text', name: 'field', label: 'Field with <special> & chars' }],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles form with very long field names', () => {
      const longName = 'field_' + 'x'.repeat(100)
      const wrapper = mount(VOvForm, {
        props: {
          options: {
            fields: [{ type: 'text', name: longName, label: 'Long Name' }],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles prop updates without errors', async () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
          loading: false,
        },
      })

      expect(wrapper.props('loading')).toBe(false)
    })

    it('handles undefined or null values in data', async () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
          data: {
            username: undefined,
            email: null,
          } as unknown as OvFormData,
        },
      })
      await wrapper.vm.$nextTick()
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Form with Translation', () => {
    it('applies translations to all field labels', () => {
      const mockT = (text?: string): string => {
        const translations: Record<string, string> = {
          Username: 'Nombre de usuario',
          Email: 'Correo electrónico',
        }
        return translations[text || ''] || text || ''
      }

      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
          t: mockT,
        },
      })

      expect(wrapper.vm).toBeDefined()
    })

    it('applies translations to action button text', () => {
      const mockT = (text?: string): string => {
        const translations: Record<string, string> = {
          submit: 'Enviar',
          cancel: 'Cancelar',
        }
        return translations[text || ''] || text || ''
      }

      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
          t: mockT,
        },
      })

      expect(wrapper.vm).toBeDefined()
    })

    it('applies translations to validation error messages', () => {
      const mockT = (text?: string): string => {
        const translations: Record<string, string> = {
          'Field is required': 'El campo es obligatorio',
          'Invalid format': 'Formato inválido',
        }
        return translations[text || ''] || text || ''
      }

      const formWithErrors: OvFormOptions = {
        fields: [{ type: 'text', name: 'field', label: 'Field' }],
        errors: [{ name: 'field', message: 'Field is required' }],
      }

      const wrapper = mount(VOvForm, {
        props: {
          options: formWithErrors,
          t: mockT,
        },
      })

      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Integration with Sandbox Example', () => {
    it('can be used with sandbox-form.vue style configuration', async () => {
      const sandboxOptions: OvFormOptions = {
        fields: [
          {
            type: 'text',
            name: 'text1',
            label: 'Text 1',
            placeholder: 'Enter some text',
            value: 'Hello World - default!',
          },
          {
            type: 'text',
            name: 'text2',
            label: 'Text 2',
            placeholder: 'Enter some text',
            rules: [{ type: 'required', params: true, message: 'Text 2 is required' }],
          },
          {
            type: 'email',
            name: 'email',
            label: 'Email',
            placeholder: 'Enter valid e-mail address',
          },
          {
            type: 'password',
            name: 'password',
            label: 'Password',
            placeholder: 'Enter password',
            required: true,
          },
          {
            type: 'textarea',
            name: 'textarea',
            label: 'Textarea',
            placeholder: 'Enter some text',
            rows: 3,
            counter: 200,
          },
          {
            type: 'select',
            name: 'select',
            label: 'Select',
            items: ['Alfa', 'Bravo', 'Charlie'],
            multiple: true,
          },
          {
            type: 'rating',
            name: 'rating',
            label: 'Rating',
            length: 5,
          },
        ],
        cols: 3,
        actions: ['validate', 'cancel', 'submit', 'reset'],
        actionAlign: 'center',
        actionSubmit: 'submit',
        actionReset: 'reset',
        actionCancel: 'cancel',
        focusFirst: true,
      }

      const wrapper = mount(VOvForm, {
        props: {
          options: sandboxOptions,
          data: {
            text1: 'Hello World!',
            email: '',
            password: '',
          },
          loading: false,
        },
      })

      await wrapper.vm.$nextTick()
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
      expect(wrapper.props('options').fields.length).toBe(7)
    })

    it('supports loading state management like sandbox-form', async () => {
      const wrapper = mount(VOvForm, {
        props: {
          options: basicFormOptions,
          loading: false,
        },
      })

      expect(wrapper.vm).toBeDefined()
      expect(wrapper.props('loading')).toBe(false)
    })
  })
})
VOvMap.test.ts
ts
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import VOvMap from '../VOvMap.vue'
import type { TOvMapMarker } from '../VOvMap.vue'

// Mock google maps library
vi.mock('vue3-google-map', () => ({
  GoogleMap: {
    name: 'GoogleMap',
    template: '<div id="google-map-mock"><slot /></div>',
    props: ['id', 'mapId', 'apiKey', 'center', 'zoom', 'autoCenter', 'autoLocation'],
    emits: ['zoom_changed', 'click'],
  },
  AdvancedMarker: {
    name: 'AdvancedMarker',
    template: '<div class="marker-mock"><slot /></div>',
    props: ['options', 'pinOptions'],
  },
  InfoWindow: {
    name: 'InfoWindow',
    template: '<div class="infowindow-mock"><slot /></div>',
    props: ['position'],
  },
}))

// Mock import.meta.env
vi.stubGlobal('import', {
  meta: {
    env: {
      VITE_GOOGLE_MAP_API_KEY: 'test-api-key',
    },
  },
})

describe('VOvMap', () => {
  beforeEach(() => {
    // Mock geolocation API
    const mockGeolocation = {
      getCurrentPosition: vi.fn((success) =>
        success({
          coords: {
            latitude: 40.7128,
            longitude: -74.006,
          },
        }),
      ),
    }
    Object.defineProperty(navigator, 'geolocation', {
      value: mockGeolocation,
      configurable: true,
    })
  })

  afterEach(() => {
    vi.clearAllMocks()
  })

  describe('Rendering', () => {
    it('mounts without error', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('renders google map component', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      const googleMap = wrapper.find('#google-map-mock')
      expect(googleMap.exists()).toBe(true)
    })

    it('renders v-overlay component', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
        },
        global: {
          stubs: {
            'v-overlay': { template: '<div class="overlay-stub"></div>' },
          },
        },
      })
      const overlay = wrapper.find('.overlay-stub')
      expect(overlay.exists()).toBe(true)
    })
  })

  describe('Props - Center', () => {
    it('accepts center prop', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 51.5074, lng: -0.1278 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.props('center')).toEqual({ lat: 51.5074, lng: -0.1278 })
    })

    it('defaults center to 0,0', () => {
      const wrapper = mount(VOvMap, {
        props: {
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.props('center')).toEqual({ lat: 0, lng: 0 })
    })

    it('updates localCenter when center prop changes', async () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      await wrapper.setProps({ center: { lat: 40.7128, lng: -74.006 } })
      // Verify the event was emitted with the new center
      expect(wrapper.emitted('centered')).toBeTruthy()
    })
  })

  describe('Props - Zoom', () => {
    it('accepts zoom prop', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 15,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.props('zoom')).toBe(15)
    })

    it('defaults zoom to 10', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.props('zoom')).toBe(10)
    })
  })

  describe('Props - Markers', () => {
    it('accepts markers prop as array', () => {
      const markers: TOvMapMarker[] = [{ lat: 51.5074, lng: -0.1278, title: 'London' }]
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          markers,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.props('markers')).toEqual(markers)
    })

    it('defaults markers to empty array', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.props('markers')).toEqual([])
    })

    it('renders markers as AdvancedMarker components', () => {
      const markers: TOvMapMarker[] = [
        { lat: 51.5074, lng: -0.1278, title: 'London' },
        { lat: 48.8566, lng: 2.3522, title: 'Paris' },
      ]
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          markers,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      const markerComponents = wrapper.findAll('.marker-mock')
      expect(markerComponents).toHaveLength(2)
    })

    it('renders marker with title', () => {
      const markers: TOvMapMarker[] = [{ lat: 51.5074, lng: -0.1278, title: 'London Tower' }]
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          markers,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      const markerComponents = wrapper.findAll('.marker-mock')
      expect(markerComponents).toHaveLength(1)
    })

    it('renders marker with custom color', () => {
      const markers: TOvMapMarker[] = [{ lat: 51.5074, lng: -0.1278, color: '#FF0000' }]
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          markers,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.props('markers')).toEqual(markers)
    })

    it('renders marker with info window content', () => {
      const markers: TOvMapMarker[] = [
        { lat: 51.5074, lng: -0.1278, info: '<p>Tower of London</p>' },
      ]
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          markers,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      const markerProps = wrapper.props('markers') as TOvMapMarker[]
      expect(markerProps?.[0]?.info).toBe('<p>Tower of London</p>')
    })
  })

  describe('Props - Auto Center', () => {
    it('accepts autoCenter prop as true', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoCenter: true,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.props('autoCenter')).toBe(true)
    })

    it('defaults autoCenter to true', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.props('autoCenter')).toBe(true)
    })
  })

  describe('Props - Auto Location', () => {
    it('accepts autoLocation prop as true', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: true,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.props('autoLocation')).toBe(true)
    })

    it('defaults autoLocation to true', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.props('autoLocation')).toBe(true)
    })
  })

  describe('Props - Dimensions', () => {
    it('accepts width prop', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          width: '500px',
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.props('width')).toBe('500px')
    })

    it('defaults width to 100%', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.props('width')).toBe('100%')
    })

    it('accepts height prop', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          height: '400px',
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.props('height')).toBe('400px')
    })

    it('defaults height to 100%', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.props('height')).toBe('100%')
    })
  })

  describe('Emits - centered', () => {
    it('emits centered event when center prop changes', async () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      const newCenter = { lat: 51.5074, lng: -0.1278 }
      await wrapper.setProps({ center: newCenter })
      await flushPromises()
      expect(wrapper.emitted('centered')).toBeTruthy()
      expect(wrapper.emitted('centered')?.[0]?.[0]).toEqual(newCenter)
    })

    it('centered event includes updated center coordinates', async () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      const newCenter = { lat: 40.7128, lng: -74.006 }
      await wrapper.setProps({ center: newCenter })
      expect(wrapper.emitted('centered')).toBeTruthy()
      const emittedCenter = wrapper.emitted('centered')?.[0]?.[0]
      expect(emittedCenter).toEqual(newCenter)
    })
  })

  describe('Emits - zoomed', () => {
    it('emits zoomed event', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
      // zoom_changed event would be triggered by GoogleMap
    })

    it('emits zoomed event when zoom prop changes', async () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      await wrapper.setProps({ zoom: 15 })
      expect(wrapper.emitted('zoomed')).toBeTruthy()
      expect(wrapper.emitted('zoomed')?.[0]?.[0]).toBe(15)
    })
  })

  describe('Emits - located', () => {
    it('emits located event on mount when autoLocation is true', async () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: true,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      await flushPromises()
      expect(wrapper.emitted('located')).toBeTruthy()
      const emitted = wrapper.emitted('located')?.[0]?.[0] as { lat: number; lng: number }
      expect(emitted.lat).toBe(40.7128)
      expect(emitted.lng).toBe(-74.006)
    })

    it('does not get location when autoLocation is false', async () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      await flushPromises()
      expect(wrapper.emitted('located')).toBeFalsy()
    })
  })

  describe('Emits - marked', () => {
    it('emits marked event when setMarkers is called', async () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      const newMarkers: TOvMapMarker[] = [{ lat: 51.5074, lng: -0.1278, title: 'London' }]
      wrapper.vm.setMarkers(newMarkers)
      await flushPromises()
      expect(wrapper.emitted('marked')).toBeTruthy()
    })

    it('emits marked event when delMarkers is called', async () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          markers: [{ lat: 51.5074, lng: -0.1278, title: 'London' }],
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      const markerToDel: TOvMapMarker[] = [{ lat: 51.5074, lng: -0.1278, title: 'London' }]
      wrapper.vm.delMarkers(markerToDel)
      await flushPromises()
      expect(wrapper.emitted('marked')).toBeTruthy()
    })

    it('emits marked event when markers prop changes', async () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          markers: [],
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      const newMarkers: TOvMapMarker[] = [{ lat: 51.5074, lng: -0.1278, title: 'London' }]
      await wrapper.setProps({ markers: newMarkers })
      expect(wrapper.emitted('marked')).toBeTruthy()
    })
  })

  describe('Emits - clicked', () => {
    it('emits clicked event with coordinates', async () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      // Simulate click event through GoogleMap component
      const googleMap = wrapper.findComponent({ name: 'GoogleMap' })
      await googleMap.vm.$emit('click', {
        latLng: {
          lat: () => 51.5074,
          lng: () => -0.1278,
        },
      })
      expect(wrapper.emitted('clicked')).toBeTruthy()
      expect(wrapper.emitted('clicked')?.[0]?.[0]).toEqual({ lat: 51.5074, lng: -0.1278 })
    })
  })

  describe('Emits - loading', () => {
    it('emits loading event with true when geolocation starts', async () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: true,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      // Initially loading should be emitted
      expect(wrapper.emitted('loading')).toBeTruthy()
    })

    it('emits loading event with false when geolocation completes', async () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: true,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      await flushPromises()
      const loadingEmits = wrapper.emitted('loading') as Array<[boolean]>
      expect(loadingEmits.length).toBeGreaterThan(0)
      // Last emit should be loading: false
      expect(loadingEmits[loadingEmits.length - 1]?.[0]).toBe(false)
    })
  })

  describe('Exposed Methods', () => {
    it('exposes getMarkers method', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.vm.getMarkers).toBeDefined()
      expect(typeof wrapper.vm.getMarkers).toBe('function')
    })

    it('exposes setMarkers method', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.vm.setMarkers).toBeDefined()
      expect(typeof wrapper.vm.setMarkers).toBe('function')
    })

    it('exposes delMarkers method', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.vm.delMarkers).toBeDefined()
      expect(typeof wrapper.vm.delMarkers).toBe('function')
    })

    it('exposes loading ref', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.vm.loading).toBeDefined()
      expect(typeof wrapper.vm.loading).toBe('boolean')
    })

    it('exposes location ref', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.vm.location).toBeDefined()
      expect(wrapper.vm.location).toHaveProperty('lat')
      expect(wrapper.vm.location).toHaveProperty('lng')
    })
  })

  describe('Marker Management', () => {
    it('getMarkers returns current markers', async () => {
      const markers: TOvMapMarker[] = [{ lat: 51.5074, lng: -0.1278, title: 'London' }]
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          markers,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      const result = wrapper.vm.getMarkers()
      expect(result).toHaveLength(1)
      expect(result[0]?.title).toBe('London')
    })

    it('setMarkers merges new markers with existing ones', async () => {
      const initialMarkers: TOvMapMarker[] = [{ lat: 51.5074, lng: -0.1278, title: 'London' }]
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          markers: initialMarkers,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      const newMarkers: TOvMapMarker[] = [{ lat: 48.8566, lng: 2.3522, title: 'Paris' }]
      wrapper.vm.setMarkers(newMarkers)
      await flushPromises()
      const result = wrapper.vm.getMarkers()
      expect(result.length).toBeGreaterThanOrEqual(1)
    })

    it('setMarkers removes duplicates', async () => {
      const initialMarkers: TOvMapMarker[] = [{ lat: 51.5074, lng: -0.1278, title: 'London' }]
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          markers: initialMarkers,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      const duplicateMarker: TOvMapMarker[] = [{ lat: 51.5074, lng: -0.1278, title: 'London' }]
      wrapper.vm.setMarkers(duplicateMarker)
      await flushPromises()
      const result = wrapper.vm.getMarkers()
      expect(result.length).toBe(1)
    })

    it('delMarkers removes markers by lat/lng', async () => {
      const initialMarkers: TOvMapMarker[] = [
        { lat: 51.5074, lng: -0.1278, title: 'London' },
        { lat: 48.8566, lng: 2.3522, title: 'Paris' },
      ]
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          markers: initialMarkers,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      const markerToDelete: TOvMapMarker[] = [{ lat: 51.5074, lng: -0.1278 }]
      wrapper.vm.delMarkers(markerToDelete)
      await flushPromises()
      const result = wrapper.vm.getMarkers()
      expect(result).toHaveLength(1)
      expect(result[0]?.title).toBe('Paris')
    })
  })

  describe('Geolocation', () => {
    it('requests user location on mount when autoLocation is true', async () => {
      const geolocationMock = vi.fn((success) =>
        success({
          coords: {
            latitude: 40.7128,
            longitude: -74.006,
          },
        }),
      )
      Object.defineProperty(navigator, 'geolocation', {
        value: { getCurrentPosition: geolocationMock },
        configurable: true,
      })

      mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: true,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      await flushPromises()
      expect(geolocationMock).toHaveBeenCalled()
    })

    it('updates location when geolocation succeeds', async () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: true,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      await flushPromises()
      expect(wrapper.vm.location).toEqual({ lat: 40.7128, lng: -74.006 })
    })

    it('centers map when geolocation succeeds and autoCenter is true', async () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: true,
          autoCenter: true,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      await flushPromises()
      // Verify centered event was emitted when geolocation succeeds
      expect(wrapper.emitted('centered')).toBeTruthy()
      const centeredEmit = wrapper.emitted('centered')?.[0]?.[0] as { lat: number; lng: number }
      expect(centeredEmit).toEqual({ lat: 40.7128, lng: -74.006 })
    })

    it('does not center map when geolocation succeeds but autoCenter is false', async () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: true,
          autoCenter: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      await flushPromises()
      // When autoCenter is false, centered event should not be emitted from geolocation
      // The component still receives location but doesn't center
      expect(wrapper.vm.location).toEqual({ lat: 40.7128, lng: -74.006 })
    })

    it('handles geolocation errors gracefully', async () => {
      const geolocationMock = vi.fn((success, error) => error(new Error('Permission denied')))
      Object.defineProperty(navigator, 'geolocation', {
        value: { getCurrentPosition: geolocationMock },
        configurable: true,
      })

      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: true,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      // Mock console.error to avoid test output noise
      const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
      await flushPromises()
      expect(wrapper.vm.loading).toBe(false)
      consoleSpy.mockRestore()
    })
  })

  describe('Mounting and Unmounting', () => {
    it('mounts and initializes correctly', async () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 40.7128, lng: -74.006 },
          zoom: 12,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('can be unmounted without errors', async () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      await wrapper.unmount()
      expect(wrapper).toBeDefined()
    })
  })

  describe('Props Updates', () => {
    it('handles center prop change', async () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      const newCenter = { lat: 51.5074, lng: -0.1278 }
      await wrapper.setProps({ center: newCenter })
      // Verify centered event was emitted
      expect(wrapper.emitted('centered')).toBeTruthy()
    })

    it('handles zoom prop change', async () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      await wrapper.setProps({ zoom: 15 })
      // Verify zoomed event was emitted
      expect(wrapper.emitted('zoomed')).toBeTruthy()
      expect(wrapper.emitted('zoomed')?.[0]?.[0]).toBe(15)
    })

    it('handles markers prop change', async () => {
      const initialMarkers: TOvMapMarker[] = []
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          markers: initialMarkers,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      const newMarkers: TOvMapMarker[] = [{ lat: 51.5074, lng: -0.1278, title: 'London' }]
      await wrapper.setProps({ markers: newMarkers })
      await flushPromises()
      const result = wrapper.vm.getMarkers()
      expect(result.length).toBeGreaterThanOrEqual(1)
    })
  })

  describe('Edge Cases', () => {
    it('handles empty markers array', () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          markers: [],
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.vm.getMarkers()).toEqual([])
    })

    it('handles marker with all optional properties', () => {
      const markers: TOvMapMarker[] = [
        {
          lat: 51.5074,
          lng: -0.1278,
          title: 'London',
          color: '#FF0000',
          info: '<p>Info</p>',
        },
      ]
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          markers,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.props('markers')).toEqual(markers)
    })

    it('handles multiple markers with same coordinates', async () => {
      const markers: TOvMapMarker[] = [
        { lat: 51.5074, lng: -0.1278, title: 'Location A' },
        { lat: 51.5074, lng: -0.1278, title: 'Location B' },
      ]
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          markers,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(wrapper.vm.getMarkers()).toHaveLength(2)
    })

    it('handles clicking with null latLng', async () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      const googleMap = wrapper.findComponent({ name: 'GoogleMap' })
      await googleMap.vm.$emit('click', { latLng: null })
      expect(wrapper.emitted('clicked')).toBeFalsy()
    })

    it('handles rapid setMarkers calls', async () => {
      const wrapper = mount(VOvMap, {
        props: {
          center: { lat: 0, lng: 0 },
          zoom: 10,
          autoLocation: false,
        },
        global: {
          stubs: {
            'v-overlay': true,
          },
        },
      })
      expect(() => {
        wrapper.vm.setMarkers([{ lat: 51.5074, lng: -0.1278 }])
        wrapper.vm.setMarkers([{ lat: 48.8566, lng: 2.3522 }])
        wrapper.vm.setMarkers([{ lat: 52.52, lng: 13.405 }])
      }).not.toThrow()
    })
  })

  describe('Type Exports', () => {
    it('exports TOvMapMarker type', () => {
      const marker: TOvMapMarker = {
        lat: 51.5074,
        lng: -0.1278,
        title: 'Test',
        color: '#FF0000',
        info: '<p>Test</p>',
      }
      expect(marker.lat).toBe(51.5074)
      expect(marker.lng).toBe(-0.1278)
    })
  })
})
VOvMedia.test.ts
ts
import { describe, it, expect, vi, beforeAll, beforeEach, afterEach } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import VOvMedia from '../VOvMedia.vue'

// Mock navigator.mediaDevices and related APIs
beforeAll(() => {
  // Mock MediaStream
  global.MediaStream = vi.fn().mockImplementation(() => ({
    getTracks: vi.fn(() => [
      {
        stop: vi.fn(),
        kind: 'video',
      },
    ]),
  })) as never

  // Mock MediaRecorder
  global.MediaRecorder = vi.fn().mockImplementation((stream) => ({
    start: vi.fn(),
    stop: vi.fn(),
    ondataavailable: null,
    onstop: null,
    stream,
  })) as never

  // Mock HTMLMediaElement methods
  HTMLMediaElement.prototype.play = vi.fn(() => Promise.resolve())
  HTMLMediaElement.prototype.pause = vi.fn()

  // Mock canvas methods
  HTMLCanvasElement.prototype.getContext = vi.fn(() => ({
    drawImage: vi.fn(),
  })) as never
  HTMLCanvasElement.prototype.toDataURL = vi.fn(() => 'data:image/png;base64,test')
  HTMLCanvasElement.prototype.toBlob = vi.fn((callback: BlobCallback) => {
    callback(new Blob(['test'], { type: 'image/png' }))
  })

  // Mock navigator.mediaDevices
  Object.defineProperty(global.navigator, 'mediaDevices', {
    value: {
      getUserMedia: vi
        .fn()
        .mockResolvedValue(new (global.MediaStream as unknown as typeof MediaStream)()),
      enumerateDevices: vi.fn().mockResolvedValue([
        {
          deviceId: 'video-device-1',
          kind: 'videoinput',
          label: 'Camera 1',
          groupId: 'group-1',
          toJSON: () => ({}),
        },
        {
          deviceId: 'audio-device-1',
          kind: 'audioinput',
          label: 'Microphone 1',
          groupId: 'group-1',
          toJSON: () => ({}),
        },
      ]),
      addEventListener: vi.fn(),
      removeEventListener: vi.fn(),
    },
    writable: true,
    configurable: true,
  })
})

describe('VOvMedia', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  afterEach(() => {
    vi.clearAllMocks()
  })

  describe('Rendering', () => {
    it('mounts without error with video prop', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
        },
      })
      await flushPromises()
      expect(wrapper.exists()).toBe(true)
    })

    it('mounts without error with audio prop', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          audio: true,
        },
      })
      await flushPromises()
      expect(wrapper.exists()).toBe(true)
    })

    it('mounts without error with both video and audio', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
          audio: true,
        },
      })
      await flushPromises()
      expect(wrapper.exists()).toBe(true)
    })

    it('mounts with src prop (no camera access needed)', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
          src: 'https://example.com/video.mp4',
        },
      })
      await flushPromises()
      expect(wrapper.exists()).toBe(true)
      expect(wrapper.props('src')).toBe('https://example.com/video.mp4')
    })

    it('renders video element when video prop is true', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
          src: 'test.mp4',
        },
      })
      await flushPromises()
      const video = wrapper.find('video')
      expect(video.exists()).toBe(true)
    })

    it('renders audio element when audio prop is true', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          audio: true,
          src: 'test.mp3',
        },
      })
      await flushPromises()
      const audio = wrapper.find('audio')
      expect(audio.exists()).toBe(true)
    })

    it('renders canvas element (hidden)', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
        },
      })
      await flushPromises()
      const canvas = wrapper.find('canvas')
      expect(canvas.exists()).toBe(true)
      expect(canvas.attributes('style')).toContain('display: none')
    })

    it('renders slot content', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
        },
        slots: {
          default: '<div>Slot content</div>',
        },
      })
      await flushPromises()
      expect(wrapper.text()).toContain('Slot content')
    })
  })

  describe('Props', () => {
    it('accepts all prop types with correct defaults', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
        },
      })
      await flushPromises()
      expect(wrapper.props('autoplay')).toBe(true)
      expect(wrapper.props('loop')).toBe(false)
      expect(wrapper.props('compact')).toBe(false)
      expect(wrapper.props('snap')).toBe(false)
      expect(wrapper.props('variant')).toBe('flat')
      expect(wrapper.props('density')).toBe('default')
      expect(wrapper.props('recorderPosition')).toBe('bottom-left')
      expect(wrapper.props('snapPosition')).toBe('top-right')
      expect(wrapper.props('format')).toBe('base64')
    })

    it('accepts custom recorder position', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
          recorderPosition: 'top-right',
        },
      })
      await flushPromises()
      expect(wrapper.props('recorderPosition')).toBe('top-right')
    })

    it('accepts custom snap position', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
          snap: true,
          snapPosition: 'bottom-right',
        },
      })
      await flushPromises()
      expect(wrapper.props('snapPosition')).toBe('bottom-right')
    })

    it('accepts media constraints', async () => {
      const constraints = { width: 1280, height: 720 }
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
          videoConstraints: constraints,
        },
      })
      await flushPromises()
      expect(wrapper.props('videoConstraints')).toEqual(constraints)
    })

    it('accepts format as base64', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
          format: 'base64',
        },
      })
      await flushPromises()
      expect(wrapper.props('format')).toBe('base64')
    })

    it('accepts format as blob', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
          format: 'blob',
        },
      })
      await flushPromises()
      expect(wrapper.props('format')).toBe('blob')
    })
  })

  describe('Emits', () => {
    it('emits loading event on mount', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
        },
      })
      await flushPromises()
      expect(wrapper.emitted('loading')).toBeTruthy()
    })

    it('emits device event with available devices', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
        },
      })
      await flushPromises()
      const deviceEmits = wrapper.emitted('device')
      expect(deviceEmits).toBeTruthy()
      expect(deviceEmits?.[0]?.[0]).toHaveProperty('devices')
      expect(deviceEmits?.[0]?.[0]).toHaveProperty('device')
    })

    it('emits started event when camera starts', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
        },
      })
      await flushPromises()
      const startedEmits = wrapper.emitted('started')
      expect(startedEmits).toBeTruthy()
    })

    it('emits error when neither video nor audio is enabled', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: false,
          audio: false,
        },
      })
      await flushPromises()
      const errorEmits = wrapper.emitted('error')
      expect(errorEmits).toBeTruthy()
    })

    it('does not emit loading when src prop is provided', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
          src: 'test.mp4',
        },
      })
      await flushPromises()
      // Should not attempt to load camera if src is provided
      expect(wrapper.props('src')).toBe('test.mp4')
    })
  })

  describe('Methods - listDevices', () => {
    it('exposes listDevices method', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
        },
      })
      await flushPromises()
      const vm = wrapper.vm as unknown as { listDevices: () => unknown }
      expect(vm.listDevices).toBeDefined()
      expect(typeof vm.listDevices).toBe('function')
    })

    it('listDevices returns video devices when video prop is true', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
        },
      })
      await flushPromises()
      const vm = wrapper.vm as unknown as { listDevices: () => unknown }
      const devices = vm.listDevices()
      expect(Array.isArray(devices)).toBe(true)
    })

    it('listDevices returns audio devices when audio prop is true', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          audio: true,
        },
      })
      await flushPromises()
      const vm = wrapper.vm as unknown as { listDevices: () => unknown }
      const devices = vm.listDevices()
      expect(Array.isArray(devices)).toBe(true)
    })
  })

  describe('Methods - setDevice', () => {
    it('exposes setDevice method', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
        },
      })
      await flushPromises()
      const vm = wrapper.vm as unknown as { setDevice: (id: string) => void }
      expect(vm.setDevice).toBeDefined()
      expect(typeof vm.setDevice).toBe('function')
    })

    it('setDevice accepts a device ID', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
        },
      })
      await flushPromises()
      const vm = wrapper.vm as unknown as { setDevice: (id: string) => void }
      expect(() => vm.setDevice('video-device-1')).not.toThrow()
    })
  })

  describe('Methods - audioPlayback', () => {
    it('exposes audioPlayback method', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          audio: true,
          src: 'test.mp3',
        },
      })
      await flushPromises()
      const vm = wrapper.vm as unknown as { audioPlayback: () => unknown }
      expect(vm.audioPlayback).toBeDefined()
      expect(typeof vm.audioPlayback).toBe('function')
    })

    it('exposes isPlaying state', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          audio: true,
          src: 'test.mp3',
        },
      })
      await flushPromises()
      const vm = wrapper.vm as unknown as { isPlaying: unknown }
      expect(vm.isPlaying).toBeDefined()
    })
  })

  describe('Methods - videoRecording', () => {
    it('exposes videoRecording method', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
        },
      })
      await flushPromises()
      const vm = wrapper.vm as unknown as { videoRecording: () => unknown }
      expect(vm.videoRecording).toBeDefined()
      expect(typeof vm.videoRecording).toBe('function')
    })
  })

  describe('UI Controls', () => {
    it('shows record button when video is enabled and no src', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
        },
      })
      await flushPromises()
      // Button should exist for recording
      const buttons = wrapper.findAll('button')
      expect(buttons.length).toBeGreaterThan(0)
    })

    it('does not show record button when src is provided', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
          src: 'test.mp4',
        },
      })
      await flushPromises()
      // Video with src shouldn't need recording button
      expect(wrapper.props('src')).toBe('test.mp4')
    })

    it('shows snap button when snap prop is true', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
          snap: true,
          src: 'test.mp4',
        },
      })
      await flushPromises()
      expect(wrapper.props('snap')).toBe(true)
    })

    it('applies recorder position classes correctly', async () => {
      const positions: Array<'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'> = [
        'top-left',
        'top-right',
        'bottom-left',
        'bottom-right',
      ]
      for (const position of positions) {
        const wrapper = mount(VOvMedia, {
          props: {
            video: true,
            recorderPosition: position,
          },
        })
        await flushPromises()
        expect(wrapper.props('recorderPosition')).toBe(position)
      }
    })

    it('applies snap position classes correctly', async () => {
      const positions: Array<'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'> = [
        'top-left',
        'top-right',
        'bottom-left',
        'bottom-right',
      ]
      for (const position of positions) {
        const wrapper = mount(VOvMedia, {
          props: {
            video: true,
            snap: true,
            snapPosition: position,
          },
        })
        await flushPromises()
        expect(wrapper.props('snapPosition')).toBe(position)
      }
    })
  })

  describe('Button Variants', () => {
    it('accepts button variant prop', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
          variant: 'outlined',
        },
      })
      await flushPromises()
      expect(wrapper.props('variant')).toBe('outlined')
    })

    it('accepts button density prop', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
          density: 'compact',
        },
      })
      await flushPromises()
      expect(wrapper.props('density')).toBe('compact')
    })

    it('renders with compact mode', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
          compact: true,
        },
      })
      await flushPromises()
      expect(wrapper.props('compact')).toBe(true)
    })
  })

  describe('Lifecycle', () => {
    it('can be unmounted without errors', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
        },
      })
      await flushPromises()
      await wrapper.unmount()
      expect(wrapper.vm).toBeDefined()
    })

    it('cleans up on unmount', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
        },
      })
      await flushPromises()
      await wrapper.unmount()
      // Component should have cleaned up without errors
      expect(wrapper.vm).toBeDefined()
    })

    it('handles mount with audio only', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          audio: true,
        },
      })
      await flushPromises()
      expect(wrapper.exists()).toBe(true)
      await wrapper.unmount()
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Media Element Attributes', () => {
    it('sets video autoplay attribute', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
          src: 'test.mp4',
          autoplay: true,
        },
      })
      await flushPromises()
      const video = wrapper.find('video')
      expect(video.exists()).toBe(true)
    })

    it('sets video loop attribute', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
          src: 'test.mp4',
          loop: true,
        },
      })
      await flushPromises()
      const video = wrapper.find('video')
      expect(video.exists()).toBe(true)
    })

    it('sets audio controls when src is provided', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          audio: true,
          src: 'test.mp3',
        },
      })
      await flushPromises()
      const audio = wrapper.find('audio')
      expect(audio.exists()).toBe(true)
    })

    it('does not show audio controls in compact mode with src', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          audio: true,
          src: 'test.mp3',
          compact: true,
        },
      })
      await flushPromises()
      const audio = wrapper.find('audio')
      expect(audio.exists()).toBe(true)
      expect(wrapper.props('compact')).toBe(true)
    })
  })

  describe('Edge Cases', () => {
    it('handles prop changes without errors', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
        },
      })
      await flushPromises()
      await wrapper.setProps({ compact: true })
      expect(wrapper.props('compact')).toBe(true)
      await flushPromises()
    })

    it('handles multiple prop updates', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
          recorderPosition: 'bottom-left',
        },
      })
      await flushPromises()
      await wrapper.setProps({ recorderPosition: 'top-right' })
      await wrapper.setProps({ snap: true })
      await wrapper.setProps({ snapPosition: 'bottom-left' })
      expect(wrapper.props('recorderPosition')).toBe('top-right')
      expect(wrapper.props('snap')).toBe(true)
      expect(wrapper.props('snapPosition')).toBe('bottom-left')
    })

    it('handles both video and audio constraints', async () => {
      const videoConstraints = { width: 1280, height: 720 }
      const audioConstraints = { echoCancellation: true }
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
          audio: true,
          videoConstraints,
          audioConstraints,
        },
      })
      await flushPromises()
      expect(wrapper.props('videoConstraints')).toEqual(videoConstraints)
      expect(wrapper.props('audioConstraints')).toEqual(audioConstraints)
    })
  })

  describe('Container Styling', () => {
    it('renders container div with correct class', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
        },
      })
      await flushPromises()
      const container = wrapper.find('.container')
      expect(container.exists()).toBe(true)
    })

    it('contains slot wrapper div', async () => {
      const wrapper = mount(VOvMedia, {
        props: {
          video: true,
        },
      })
      await flushPromises()
      const slot = wrapper.find('.slot')
      expect(slot.exists()).toBe(true)
    })
  })
})
VOvPad.test.ts
ts
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import VOvPad from '../VOvPad.vue'

// Mock canvas context and related browser APIs
beforeEach(() => {
  // Mock HTMLCanvasElement.getContext
  HTMLCanvasElement.prototype.getContext = vi.fn(
    () =>
      ({
        clearRect: vi.fn(),
        fillRect: vi.fn(),
        beginPath: vi.fn(),
        moveTo: vi.fn(),
        lineTo: vi.fn(),
        stroke: vi.fn(),
        fill: vi.fn(),
        closePath: vi.fn(),
        setLineDash: vi.fn(),
        ellipse: vi.fn(),
        drawImage: vi.fn(),
        fillStyle: '',
        strokeStyle: '',
        lineWidth: 1,
        lineJoin: 'miter' as CanvasLineJoin,
        lineCap: 'round' as CanvasLineCap,
        globalCompositeOperation: 'source-over',
        setTransform: vi.fn(),
      }) as unknown as CanvasRenderingContext2D,
  ) as unknown as HTMLCanvasElement['getContext']

  // Mock ResizeObserver
  global.ResizeObserver = vi.fn().mockImplementation(() => ({
    observe: vi.fn(),
    disconnect: vi.fn(),
  })) as unknown as typeof ResizeObserver

  // Mock Image
  global.Image = vi.fn().mockImplementation(() => ({
    src: '',
  })) as unknown as typeof Image
})

afterEach(() => {
  vi.clearAllMocks()
})

describe('VOvPad', () => {
  describe('Rendering', () => {
    it('mounts without error', () => {
      const wrapper = mount(VOvPad, {
        props: {
          width: '100%',
          height: '240px',
        },
      })
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('renders canvas element', () => {
      const wrapper = mount(VOvPad, {
        props: {
          width: '100%',
          height: '240px',
        },
      })
      const canvas = wrapper.find('canvas')
      expect(canvas.exists()).toBe(true)
    })

    it('renders container div', () => {
      const wrapper = mount(VOvPad, {
        props: {
          width: '100%',
          height: '240px',
        },
      })
      const container = wrapper.find('#container')
      expect(container.exists()).toBe(true)
    })
  })

  describe('Props - Dimensions', () => {
    it('accepts width prop as string', () => {
      const wrapper = mount(VOvPad, {
        props: {
          width: '100%',
          height: '240px',
        },
      })
      expect(wrapper.props('width')).toBe('100%')
    })

    it('accepts height prop as string', () => {
      const wrapper = mount(VOvPad, {
        props: {
          width: '100%',
          height: '240px',
        },
      })
      expect(wrapper.props('height')).toBe('240px')
    })

    it('accepts width prop as number', () => {
      const wrapper = mount(VOvPad, {
        props: {
          width: 500,
          height: 300,
        },
      })
      expect(wrapper.props('width')).toBe(500)
    })

    it('accepts height prop as number', () => {
      const wrapper = mount(VOvPad, {
        props: {
          width: 500,
          height: 300,
        },
      })
      expect(wrapper.props('height')).toBe(300)
    })

    it('defaults to 100% width and height', () => {
      const wrapper = mount(VOvPad)
      expect(wrapper.props('width')).toBe('100%')
      expect(wrapper.props('height')).toBe('100%')
    })
  })

  describe('Props - Drawing Settings', () => {
    it('accepts color prop', () => {
      const wrapper = mount(VOvPad, {
        props: {
          color: '#FF0000',
        },
      })
      expect(wrapper.props('color')).toBe('#FF0000')
    })

    it('defaults color to black', () => {
      const wrapper = mount(VOvPad)
      expect(wrapper.props('color')).toBe('#000000')
    })

    it('accepts lineWidth prop', () => {
      const wrapper = mount(VOvPad, {
        props: {
          lineWidth: 10,
        },
      })
      expect(wrapper.props('lineWidth')).toBe(10)
    })

    it('defaults lineWidth to 5', () => {
      const wrapper = mount(VOvPad)
      expect(wrapper.props('lineWidth')).toBe(5)
    })

    it('accepts backgroundColor prop', () => {
      const wrapper = mount(VOvPad, {
        props: {
          backgroundColor: '#CCCCCC',
        },
      })
      expect(wrapper.props('backgroundColor')).toBe('#CCCCCC')
    })

    it('defaults backgroundColor to white', () => {
      const wrapper = mount(VOvPad)
      expect(wrapper.props('backgroundColor')).toBe('#FFFFFF')
    })

    it('accepts lineCap prop with valid values', () => {
      const wrapper = mount(VOvPad, {
        props: {
          lineCap: 'square',
        },
      })
      expect(wrapper.props('lineCap')).toBe('square')
    })

    it('defaults lineCap to round', () => {
      const wrapper = mount(VOvPad)
      expect(wrapper.props('lineCap')).toBe('round')
    })

    it('accepts lineJoin prop with valid values', () => {
      const wrapper = mount(VOvPad, {
        props: {
          lineJoin: 'round',
        },
      })
      expect(wrapper.props('lineJoin')).toBe('round')
    })

    it('defaults lineJoin to miter', () => {
      const wrapper = mount(VOvPad)
      expect(wrapper.props('lineJoin')).toBe('miter')
    })
  })

  describe('Props - Stroke Type', () => {
    it('accepts strokeType prop with dash', () => {
      const wrapper = mount(VOvPad, {
        props: {
          strokeType: 'dash',
        },
      })
      expect(wrapper.props('strokeType')).toBe('dash')
    })

    it('accepts strokeType prop with line', () => {
      const wrapper = mount(VOvPad, {
        props: {
          strokeType: 'line',
        },
      })
      expect(wrapper.props('strokeType')).toBe('line')
    })

    it('accepts strokeType prop with square', () => {
      const wrapper = mount(VOvPad, {
        props: {
          strokeType: 'square',
        },
      })
      expect(wrapper.props('strokeType')).toBe('square')
    })

    it('accepts strokeType prop with circle', () => {
      const wrapper = mount(VOvPad, {
        props: {
          strokeType: 'circle',
        },
      })
      expect(wrapper.props('strokeType')).toBe('circle')
    })

    it('accepts strokeType prop with triangle', () => {
      const wrapper = mount(VOvPad, {
        props: {
          strokeType: 'triangle',
        },
      })
      expect(wrapper.props('strokeType')).toBe('triangle')
    })

    it('accepts strokeType prop with half_triangle', () => {
      const wrapper = mount(VOvPad, {
        props: {
          strokeType: 'half_triangle',
        },
      })
      expect(wrapper.props('strokeType')).toBe('half_triangle')
    })

    it('defaults strokeType to dash', () => {
      const wrapper = mount(VOvPad)
      expect(wrapper.props('strokeType')).toBe('dash')
    })
  })

  describe('Props - Shape Filling', () => {
    it('accepts fillShape prop as true', () => {
      const wrapper = mount(VOvPad, {
        props: {
          fillShape: true,
          strokeType: 'circle',
        },
      })
      expect(wrapper.props('fillShape')).toBe(true)
    })

    it('accepts fillShape prop as false', () => {
      const wrapper = mount(VOvPad, {
        props: {
          fillShape: false,
        },
      })
      expect(wrapper.props('fillShape')).toBe(false)
    })

    it('defaults fillShape to false', () => {
      const wrapper = mount(VOvPad)
      expect(wrapper.props('fillShape')).toBe(false)
    })
  })

  describe('Props - Eraser', () => {
    it('accepts eraser prop as true', () => {
      const wrapper = mount(VOvPad, {
        props: {
          eraser: true,
        },
      })
      expect(wrapper.props('eraser')).toBe(true)
    })

    it('accepts eraser prop as false', () => {
      const wrapper = mount(VOvPad, {
        props: {
          eraser: false,
        },
      })
      expect(wrapper.props('eraser')).toBe(false)
    })

    it('defaults eraser to false', () => {
      const wrapper = mount(VOvPad)
      expect(wrapper.props('eraser')).toBe(false)
    })

    it('renders canvas with data-eraser attribute when eraser is true', async () => {
      const wrapper = mount(VOvPad, {
        props: {
          eraser: true,
        },
      })
      await wrapper.vm.$nextTick()
      const canvas = wrapper.find('canvas')
      expect(canvas.attributes('data-eraser')).toBe('true')
    })

    it('renders canvas with data-eraser attribute as false when eraser is false', async () => {
      const wrapper = mount(VOvPad, {
        props: {
          eraser: false,
        },
      })
      await wrapper.vm.$nextTick()
      const canvas = wrapper.find('canvas')
      expect(canvas.attributes('data-eraser')).toBe('false')
    })
  })

  describe('Props - Lock', () => {
    it('accepts lock prop as true', () => {
      const wrapper = mount(VOvPad, {
        props: {
          lock: true,
        },
      })
      expect(wrapper.props('lock')).toBe(true)
    })

    it('accepts lock prop as false', () => {
      const wrapper = mount(VOvPad, {
        props: {
          lock: false,
        },
      })
      expect(wrapper.props('lock')).toBe(false)
    })

    it('defaults lock to false', () => {
      const wrapper = mount(VOvPad)
      expect(wrapper.props('lock')).toBe(false)
    })
  })

  describe('Props - Canvas ID', () => {
    it('accepts custom canvasId prop', () => {
      const wrapper = mount(VOvPad, {
        props: {
          canvasId: 'my-custom-canvas',
        },
      })
      expect(wrapper.props('canvasId')).toBe('my-custom-canvas')
    })

    it('generates canvasId with random suffix by default', () => {
      const wrapper = mount(VOvPad)
      const id = wrapper.props('canvasId')
      expect(id).toMatch(/^canvas-/)
      expect(typeof id).toBe('string')
    })
  })

  describe('Props - Output Format', () => {
    it('accepts saveAs prop as jpeg', () => {
      const wrapper = mount(VOvPad, {
        props: {
          saveAs: 'jpeg',
        },
      })
      expect(wrapper.props('saveAs')).toBe('jpeg')
    })

    it('accepts saveAs prop as png', () => {
      const wrapper = mount(VOvPad, {
        props: {
          saveAs: 'png',
        },
      })
      expect(wrapper.props('saveAs')).toBe('png')
    })

    it('defaults saveAs to png', () => {
      const wrapper = mount(VOvPad)
      expect(wrapper.props('saveAs')).toBe('png')
    })
  })

  describe('Props - Output Dimensions', () => {
    it('accepts outputWidth prop', () => {
      const wrapper = mount(VOvPad, {
        props: {
          outputWidth: 800,
        },
      })
      expect(wrapper.props('outputWidth')).toBe(800)
    })

    it('accepts outputHeight prop', () => {
      const wrapper = mount(VOvPad, {
        props: {
          outputHeight: 600,
        },
      })
      expect(wrapper.props('outputHeight')).toBe(600)
    })
  })

  describe('Props - Images', () => {
    it('accepts initialImage prop as array', () => {
      const initialImage: Array<never> = []
      const wrapper = mount(VOvPad, {
        props: {
          initialImage,
        },
      })
      expect(wrapper.props('initialImage')).toEqual([])
    })

    it('accepts additionalImages prop as array', () => {
      const additionalImages: Array<never> = []
      const wrapper = mount(VOvPad, {
        props: {
          additionalImages,
        },
      })
      expect(wrapper.props('additionalImages')).toEqual([])
    })

    it('accepts backgroundImage prop', () => {
      const wrapper = mount(VOvPad, {
        props: {
          backgroundImage: 'https://example.com/image.png',
        },
      })
      expect(wrapper.props('backgroundImage')).toBe('https://example.com/image.png')
    })

    it('defaults backgroundImage to null', () => {
      const wrapper = mount(VOvPad)
      expect(wrapper.props('backgroundImage')).toBeNull()
    })
  })

  describe('Exposed Methods', () => {
    it('exposes clear method', () => {
      const wrapper = mount(VOvPad)
      expect(wrapper.vm.clear).toBeDefined()
      expect(typeof wrapper.vm.clear).toBe('function')
    })

    it('exposes reset method', () => {
      const wrapper = mount(VOvPad)
      expect(wrapper.vm.reset).toBeDefined()
      expect(typeof wrapper.vm.reset).toBe('function')
    })

    it('exposes undo method', () => {
      const wrapper = mount(VOvPad)
      expect(wrapper.vm.undo).toBeDefined()
      expect(typeof wrapper.vm.undo).toBe('function')
    })

    it('exposes redo method', () => {
      const wrapper = mount(VOvPad)
      expect(wrapper.vm.redo).toBeDefined()
      expect(typeof wrapper.vm.redo).toBe('function')
    })

    it('exposes save method', () => {
      const wrapper = mount(VOvPad)
      expect(wrapper.vm.save).toBeDefined()
      expect(typeof wrapper.vm.save).toBe('function')
    })

    it('exposes startDraw method', () => {
      const wrapper = mount(VOvPad)
      expect(wrapper.vm.startDraw).toBeDefined()
      expect(typeof wrapper.vm.startDraw).toBe('function')
    })

    it('exposes draw method', () => {
      const wrapper = mount(VOvPad)
      expect(wrapper.vm.draw).toBeDefined()
      expect(typeof wrapper.vm.draw).toBe('function')
    })

    it('exposes stopDraw method', () => {
      const wrapper = mount(VOvPad)
      expect(wrapper.vm.stopDraw).toBeDefined()
      expect(typeof wrapper.vm.stopDraw).toBe('function')
    })

    it('exposes handleResize method', () => {
      const wrapper = mount(VOvPad)
      expect(wrapper.vm.handleResize).toBeDefined()
      expect(typeof wrapper.vm.handleResize).toBe('function')
    })
  })

  describe('Reset Functionality', () => {
    it('can call reset method when not locked', () => {
      const wrapper = mount(VOvPad, {
        props: {
          lock: false,
        },
      })
      expect(() => wrapper.vm.reset()).not.toThrow()
    })

    it('cannot call reset when locked', () => {
      const wrapper = mount(VOvPad, {
        props: {
          lock: true,
        },
      })
      expect(() => wrapper.vm.reset()).not.toThrow()
    })
  })

  describe('Undo/Redo Functionality', () => {
    it('can call undo method when not locked', () => {
      const wrapper = mount(VOvPad, {
        props: {
          lock: false,
        },
      })
      expect(() => wrapper.vm.undo()).not.toThrow()
    })

    it('can call redo method when not locked', () => {
      const wrapper = mount(VOvPad, {
        props: {
          lock: false,
        },
      })
      expect(() => wrapper.vm.redo()).not.toThrow()
    })

    it('cannot call undo when locked', () => {
      const wrapper = mount(VOvPad, {
        props: {
          lock: true,
        },
      })
      expect(() => wrapper.vm.undo()).not.toThrow()
    })

    it('cannot call redo when locked', () => {
      const wrapper = mount(VOvPad, {
        props: {
          lock: true,
        },
      })
      expect(() => wrapper.vm.redo()).not.toThrow()
    })
  })

  describe('Save Functionality', () => {
    it('save method can be called without error', () => {
      const wrapper = mount(VOvPad)
      expect(() => wrapper.vm.save()).not.toThrow()
    })

    it('emits update:image event on save', () => {
      const wrapper = mount(VOvPad)
      wrapper.vm.save()
      expect(wrapper.emitted('update:image')).toBeTruthy()
    })

    it('respects saveAs prop format in emitted image', () => {
      const wrapper = mount(VOvPad, {
        props: {
          saveAs: 'jpeg',
        },
      })
      wrapper.vm.save()
      const emitted = wrapper.emitted('update:image')
      expect(emitted).toBeTruthy()
    })
  })

  describe('Canvas Event Handlers', () => {
    it('canvas element exists and is rendered', async () => {
      const wrapper = mount(VOvPad)
      const canvas = wrapper.find('canvas')
      expect(canvas.exists()).toBe(true)
    })

    it('canvas has pointer event handlers attached', async () => {
      const wrapper = mount(VOvPad)
      const canvas = wrapper.find('canvas')
      expect(canvas.exists()).toBe(true)
      // Event handlers are attached in the template via Vue directives
    })
  })

  describe('Touch Action Prevention', () => {
    it('container element exists for touch event handling', async () => {
      const wrapper = mount(VOvPad)
      const container = wrapper.find('#container')
      expect(container.exists()).toBe(true)
      // Touch move prevention is handled via @touchmove.prevent in template
    })
  })

  describe('Canvas Style', () => {
    it('canvas is rendered with proper attributes', () => {
      const wrapper = mount(VOvPad)
      const canvas = wrapper.find('canvas')
      expect(canvas.exists()).toBe(true)
      // Canvas has width and height attributes set dynamically
      expect(canvas.element).toBeDefined()
    })
  })

  describe('Mounting and Unmounting', () => {
    it('mounts and initializes correctly', async () => {
      const wrapper = mount(VOvPad, {
        props: {
          width: '100%',
          height: '240px',
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('can be unmounted without errors', async () => {
      const wrapper = mount(VOvPad)
      await wrapper.unmount()
      expect(wrapper).toBeDefined()
    })

    it('removes resize listener on unmount', async () => {
      const removeEventListenerSpy = vi.spyOn(window, 'removeEventListener')
      const wrapper = mount(VOvPad)
      await wrapper.unmount()
      expect(removeEventListenerSpy).toHaveBeenCalledWith('resize', expect.any(Function))
      removeEventListenerSpy.mockRestore()
    })
  })

  describe('Props Updates', () => {
    it('handles color prop change', async () => {
      const wrapper = mount(VOvPad, {
        props: {
          color: '#000000',
        },
      })
      await wrapper.setProps({ color: '#FF0000' })
      expect(wrapper.props('color')).toBe('#FF0000')
    })

    it('handles lineWidth prop change', async () => {
      const wrapper = mount(VOvPad, {
        props: {
          lineWidth: 5,
        },
      })
      await wrapper.setProps({ lineWidth: 10 })
      expect(wrapper.props('lineWidth')).toBe(10)
    })

    it('handles backgroundColor prop change', async () => {
      const wrapper = mount(VOvPad, {
        props: {
          backgroundColor: '#FFFFFF',
        },
      })
      await wrapper.setProps({ backgroundColor: '#CCCCCC' })
      expect(wrapper.props('backgroundColor')).toBe('#CCCCCC')
      await flushPromises()
    })

    it('handles eraser prop change', async () => {
      const wrapper = mount(VOvPad, {
        props: {
          eraser: false,
        },
      })
      await wrapper.setProps({ eraser: true })
      expect(wrapper.props('eraser')).toBe(true)
    })

    it('handles strokeType prop change', async () => {
      const wrapper = mount(VOvPad, {
        props: {
          strokeType: 'dash',
        },
      })
      await wrapper.setProps({ strokeType: 'circle' })
      expect(wrapper.props('strokeType')).toBe('circle')
    })
  })

  describe('Integration with Sandbox Example', () => {
    it('can be used with all sandbox props like sandbox-pad.vue example', async () => {
      const wrapper = mount(VOvPad, {
        props: {
          width: '100%',
          height: '240px',
          lock: false,
          eraser: false,
          backgroundColor: 'white',
          backgroundImage: '',
          lineWidth: 5,
          color: '#000000',
        },
      })
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.props('width')).toBe('100%')
      expect(wrapper.props('height')).toBe('240px')
      expect(wrapper.props('lock')).toBe(false)
      expect(wrapper.props('eraser')).toBe(false)
      expect(wrapper.props('backgroundColor')).toBe('white')
      expect(wrapper.props('lineWidth')).toBe(5)
      expect(wrapper.props('color')).toBe('#000000')
    })

    it('supports all control methods used in sandbox-pad.vue example', () => {
      const wrapper = mount(VOvPad)
      expect(typeof wrapper.vm.reset).toBe('function')
      expect(typeof wrapper.vm.undo).toBe('function')
      expect(typeof wrapper.vm.redo).toBe('function')
      expect(typeof wrapper.vm.save).toBe('function')
    })
  })

  describe('Edge Cases', () => {
    it('handles props with undefined values', () => {
      const wrapper = mount(VOvPad, {
        props: {
          color: undefined,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles multiple rapid save calls', () => {
      const wrapper = mount(VOvPad)
      expect(() => {
        wrapper.vm.save()
        wrapper.vm.save()
        wrapper.vm.save()
      }).not.toThrow()
    })

    it('handles canvas resize event', async () => {
      const wrapper = mount(VOvPad)
      expect(() => wrapper.vm.handleResize()).not.toThrow()
    })
  })
})
VOvShare.test.ts
ts
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import VOvShare, { type Share } from '../VOvShare.vue'

// Mock vue-socials components
vi.mock('vue-socials', () => ({
  STwitter: {
    name: 'STwitter',
    template: '<div><slot /></div>',
  },
  SFacebook: {
    name: 'SFacebook',
    template: '<div><slot /></div>',
  },
  SLinkedIn: {
    name: 'SLinkedIn',
    template: '<div><slot /></div>',
  },
  SWhatsApp: {
    name: 'SWhatsApp',
    template: '<div><slot /></div>',
  },
}))

describe('VOvShare', () => {
  beforeEach(() => {
    // Mock navigator.clipboard
    Object.assign(navigator, {
      clipboard: {
        writeText: vi.fn(() => Promise.resolve()),
      },
    })
    // Mock document.execCommand - it doesn't exist in jsdom by default
    if (!document.execCommand) {
      document.execCommand = vi.fn(() => true)
    }
  })

  afterEach(() => {
    vi.clearAllMocks()
  })

  describe('Rendering', () => {
    it('mounts without error', () => {
      const wrapper = mount(VOvShare)
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('renders v-defaults-provider wrapper', () => {
      const wrapper = mount(VOvShare)
      expect(wrapper.findComponent({ name: 'VDefaultsProvider' }).exists()).toBe(true)
    })
  })

  describe('Props - Share', () => {
    it('defaults to all share options', () => {
      const wrapper = mount(VOvShare)
      expect(wrapper.props('share')).toEqual([
        'twitter',
        'facebook',
        'linkedin',
        'whatsapp',
        'copy',
      ])
    })

    it('accepts custom share array', () => {
      const customShare: Share[] = ['twitter', 'facebook']
      const wrapper = mount(VOvShare, {
        props: {
          share: customShare,
        },
      })
      expect(wrapper.props('share')).toEqual(customShare)
    })

    it('accepts empty share array', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: [],
        },
      })
      expect(wrapper.props('share')).toEqual([])
    })

    it('accepts individual share options', () => {
      const validOptions: Share[] = ['twitter', 'facebook', 'linkedin', 'whatsapp', 'copy']
      validOptions.forEach((option) => {
        const wrapper = mount(VOvShare, {
          props: {
            share: [option],
          },
        })
        expect(wrapper.props('share')).toContain(option)
      })
    })
  })

  describe('Props - Window Features', () => {
    it('accepts windowFeatures prop', () => {
      const windowFeatures = { width: 600, height: 400, top: 100, left: 100 }
      const wrapper = mount(VOvShare, {
        props: {
          windowFeatures,
        },
      })
      expect(wrapper.props('windowFeatures')).toEqual(windowFeatures)
    })

    it('defaults windowFeatures to empty object', () => {
      const wrapper = mount(VOvShare)
      expect(wrapper.props('windowFeatures')).toEqual({ url: '' })
    })

    it('accepts windowFeatures with all properties', () => {
      const windowFeatures = { width: 800, height: 600, top: 50, left: 50 }
      const wrapper = mount(VOvShare, {
        props: {
          windowFeatures,
        },
      })
      const features = wrapper.props('windowFeatures') as typeof windowFeatures
      expect(features.width).toBe(800)
      expect(features.height).toBe(600)
      expect(features.top).toBe(50)
      expect(features.left).toBe(50)
    })
  })

  describe('Props - Share Options', () => {
    it('accepts shareOptions prop', () => {
      const shareOptions = {
        text: 'Check this out!',
        url: 'https://example.com',
        via: 'username',
        hashtags: ['tag1', 'tag2'],
        number: '1234567890',
      }
      const wrapper = mount(VOvShare, {
        props: {
          shareOptions,
        },
      })
      expect(wrapper.props('shareOptions')).toEqual(shareOptions)
    })

    it('defaults shareOptions to empty object', () => {
      const wrapper = mount(VOvShare)
      expect(wrapper.props('shareOptions')).toEqual({ number: '' })
    })

    it('accepts shareOptions with all optional properties', () => {
      const shareOptions = {
        text: 'Share text',
        url: 'https://example.com',
        via: 'twitter_handle',
        hashtags: ['tag1'],
        quote: 'Quote text',
        number: '123456',
      }
      const wrapper = mount(VOvShare, {
        props: {
          shareOptions,
        },
      })
      const options = wrapper.props('shareOptions') as typeof shareOptions
      expect(options.text).toBe('Share text')
      expect(options.url).toBe('https://example.com')
      expect(options.via).toBe('twitter_handle')
      expect(options.hashtags).toContain('tag1')
      expect(options.quote).toBe('Quote text')
      expect(options.number).toBe('123456')
    })
  })

  describe('Props - Behavior', () => {
    it('accepts useNativeBehavior prop as true', () => {
      const wrapper = mount(VOvShare, {
        props: {
          useNativeBehavior: true,
        },
      })
      expect(wrapper.props('useNativeBehavior')).toBe(true)
    })

    it('accepts useNativeBehavior prop as false', () => {
      const wrapper = mount(VOvShare, {
        props: {
          useNativeBehavior: false,
        },
      })
      expect(wrapper.props('useNativeBehavior')).toBe(false)
    })

    it('defaults useNativeBehavior to false', () => {
      const wrapper = mount(VOvShare)
      expect(wrapper.props('useNativeBehavior')).toBe(false)
    })
  })

  describe('Props - Button Styling', () => {
    it('accepts variant prop', () => {
      const variants: Array<'flat' | 'text' | 'elevated' | 'tonal' | 'outlined' | 'plain'> = [
        'flat',
        'text',
        'elevated',
        'tonal',
        'outlined',
        'plain',
      ]
      variants.forEach((variant) => {
        const wrapper = mount(VOvShare, {
          props: {
            variant,
          },
        })
        expect(wrapper.props('variant')).toBe(variant)
      })
    })

    it('defaults variant to undefined', () => {
      const wrapper = mount(VOvShare)
      expect(wrapper.props('variant')).toBeUndefined()
    })

    it('accepts density prop', () => {
      const densities: Array<'default' | 'comfortable' | 'compact'> = [
        'default',
        'comfortable',
        'compact',
      ]
      densities.forEach((density) => {
        const wrapper = mount(VOvShare, {
          props: {
            density,
          },
        })
        expect(wrapper.props('density')).toBe(density)
      })
    })

    it('defaults density to undefined', () => {
      const wrapper = mount(VOvShare)
      expect(wrapper.props('density')).toBeUndefined()
    })

    it('accepts color prop', () => {
      const wrapper = mount(VOvShare, {
        props: {
          color: 'primary',
        },
      })
      expect(wrapper.props('color')).toBe('primary')
    })

    it('defaults color to undefined', () => {
      const wrapper = mount(VOvShare)
      expect(wrapper.props('color')).toBeUndefined()
    })
  })

  describe('Social Media Components Rendering', () => {
    it('renders Twitter button when twitter is in share array', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['twitter'],
        },
      })
      expect(wrapper.findComponent({ name: 'STwitter' }).exists()).toBe(true)
    })

    it('does not render Twitter button when twitter is not in share array', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['facebook'],
        },
      })
      expect(wrapper.findComponent({ name: 'STwitter' }).exists()).toBe(false)
    })

    it('renders Facebook button when facebook is in share array', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['facebook'],
        },
      })
      expect(wrapper.findComponent({ name: 'SFacebook' }).exists()).toBe(true)
    })

    it('does not render Facebook button when facebook is not in share array', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['twitter'],
        },
      })
      expect(wrapper.findComponent({ name: 'SFacebook' }).exists()).toBe(false)
    })

    it('renders LinkedIn button when linkedin is in share array', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['linkedin'],
        },
      })
      expect(wrapper.findComponent({ name: 'SLinkedIn' }).exists()).toBe(true)
    })

    it('does not render LinkedIn button when linkedin is not in share array', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['twitter'],
        },
      })
      expect(wrapper.findComponent({ name: 'SLinkedIn' }).exists()).toBe(false)
    })

    it('renders WhatsApp button when whatsapp is in share array', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['whatsapp'],
        },
      })
      expect(wrapper.findComponent({ name: 'SWhatsApp' }).exists()).toBe(true)
    })

    it('does not render WhatsApp button when whatsapp is not in share array', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['twitter'],
        },
      })
      expect(wrapper.findComponent({ name: 'SWhatsApp' }).exists()).toBe(false)
    })

    it('renders all social media buttons when all are in share array', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['twitter', 'facebook', 'linkedin', 'whatsapp'],
        },
      })
      expect(wrapper.findComponent({ name: 'STwitter' }).exists()).toBe(true)
      expect(wrapper.findComponent({ name: 'SFacebook' }).exists()).toBe(true)
      expect(wrapper.findComponent({ name: 'SLinkedIn' }).exists()).toBe(true)
      expect(wrapper.findComponent({ name: 'SWhatsApp' }).exists()).toBe(true)
    })

    it('renders no social media buttons when share array is empty', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: [],
        },
      })
      expect(wrapper.findComponent({ name: 'STwitter' }).exists()).toBe(false)
      expect(wrapper.findComponent({ name: 'SFacebook' }).exists()).toBe(false)
      expect(wrapper.findComponent({ name: 'SLinkedIn' }).exists()).toBe(false)
      expect(wrapper.findComponent({ name: 'SWhatsApp' }).exists()).toBe(false)
    })
  })

  describe('Copy Button', () => {
    it('renders copy button when copy is in share array', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['copy'],
        },
      })
      const copyBtn = wrapper.find('[data-cy="v-bsb-share-copy"]')
      expect(copyBtn.exists()).toBe(true)
    })

    it('does not render copy button when copy is not in share array', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['twitter'],
        },
      })
      const copyBtn = wrapper.find('[data-cy="v-bsb-share-copy"]')
      expect(copyBtn.exists()).toBe(false)
    })

    it('copy button has data-cy attribute when rendered', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['copy'],
        },
      })
      const copyBtn = wrapper.find('[data-cy="v-bsb-share-copy"]')
      expect(copyBtn.exists()).toBe(true)
      expect(copyBtn.attributes('data-cy')).toBe('v-bsb-share-copy')
    })

    it('copy button is clickable', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['copy'],
        },
      })
      const copyBtn = wrapper.find('[data-cy="v-bsb-share-copy"]')
      expect(copyBtn.exists()).toBe(true)
    })
  })

  describe('Copy to Clipboard Functionality', () => {
    it('copies text to clipboard when copy button is clicked', async () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['copy'],
          shareOptions: {
            text: 'Text to copy',
            url: 'https://example.com',
            number: '123',
          },
        },
      })
      const copyBtn = wrapper.find('[data-cy="v-bsb-share-copy"]')
      await copyBtn.trigger('click')
      await flushPromises()
      expect(navigator.clipboard.writeText).toHaveBeenCalledWith('Text to copy')
    })

    it('copies empty string when shareOptions.text is not provided', async () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['copy'],
          shareOptions: {
            url: 'https://example.com',
            number: '123',
          },
        },
      })
      const copyBtn = wrapper.find('[data-cy="v-bsb-share-copy"]')
      await copyBtn.trigger('click')
      await flushPromises()
      expect(navigator.clipboard.writeText).toHaveBeenCalledWith('')
    })

    it('uses fallback method when navigator.clipboard is not available', async () => {
      // Mock missing clipboard
      const originalClipboard = navigator.clipboard
      Object.defineProperty(navigator, 'clipboard', {
        value: undefined,
        writable: true,
      })

      const wrapper = mount(VOvShare, {
        props: {
          share: ['copy'],
          shareOptions: {
            text: 'Fallback copy test',
            url: 'https://example.com',
            number: '123',
          },
        },
      })
      const copyBtn = wrapper.find('[data-cy="v-bsb-share-copy"]')
      await copyBtn.trigger('click')
      await flushPromises()
      expect(document.execCommand).toHaveBeenCalledWith('copy')

      // Restore clipboard
      Object.defineProperty(navigator, 'clipboard', {
        value: originalClipboard,
        writable: true,
      })
    })

    it('handles clipboard API errors gracefully', async () => {
      const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
      Object.assign(navigator, {
        clipboard: {
          writeText: vi.fn(() => Promise.reject(new Error('Clipboard error'))),
        },
      })

      const wrapper = mount(VOvShare, {
        props: {
          share: ['copy'],
          shareOptions: {
            text: 'Error test',
            url: 'https://example.com',
            number: '123',
          },
        },
      })
      const copyBtn = wrapper.find('[data-cy="v-bsb-share-copy"]')
      await copyBtn.trigger('click')
      await flushPromises()
      expect(consoleErrorSpy).toHaveBeenCalled()
      consoleErrorSpy.mockRestore()
    })
  })

  describe('Social Media Button Icons', () => {
    it('Twitter button element exists when twitter is in share array', async () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['twitter'],
        },
      })
      await flushPromises()
      const twitterBtn = wrapper.find('[data-cy="v-bsb-share-twitter"]')
      expect(twitterBtn.exists()).toBe(true)
    })

    it('Facebook button element exists when facebook is in share array', async () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['facebook'],
        },
      })
      await flushPromises()
      const facebookBtn = wrapper.find('[data-cy="v-bsb-share-facebook"]')
      expect(facebookBtn.exists()).toBe(true)
    })

    it('LinkedIn button element exists when linkedin is in share array', async () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['linkedin'],
        },
      })
      await flushPromises()
      const linkedInBtn = wrapper.find('[data-cy="v-bsb-share-linkedin"]')
      expect(linkedInBtn.exists()).toBe(true)
    })

    it('WhatsApp button element exists when whatsapp is in share array', async () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['whatsapp'],
        },
      })
      await flushPromises()
      const whatsappBtn = wrapper.find('[data-cy="v-bsb-share-whatsapp"]')
      expect(whatsappBtn.exists()).toBe(true)
    })
  })

  describe('V-Defaults-Provider Props', () => {
    it('passes variant to v-defaults-provider when defined', () => {
      const wrapper = mount(VOvShare, {
        props: {
          variant: 'flat',
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('passes density to v-defaults-provider when defined', () => {
      const wrapper = mount(VOvShare, {
        props: {
          density: 'compact',
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('passes color to v-defaults-provider when defined', () => {
      const wrapper = mount(VOvShare, {
        props: {
          color: 'error',
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('passes all styling props to v-defaults-provider', () => {
      const wrapper = mount(VOvShare, {
        props: {
          variant: 'outlined',
          density: 'comfortable',
          color: 'warning',
        },
      })
      expect(wrapper.props('variant')).toBe('outlined')
      expect(wrapper.props('density')).toBe('comfortable')
      expect(wrapper.props('color')).toBe('warning')
    })
  })

  describe('Props Passing to Social Components', () => {
    it('passes windowFeatures to social media components', () => {
      const windowFeatures = { width: 700, height: 500, top: 75, left: 75 }
      const wrapper = mount(VOvShare, {
        props: {
          share: ['twitter', 'facebook'],
          windowFeatures,
        },
      })
      expect(wrapper.props('windowFeatures')).toEqual(windowFeatures)
    })

    it('passes shareOptions to social media components', () => {
      const shareOptions = {
        text: 'Shared text',
        url: 'https://example.com',
        via: 'handle',
        hashtags: ['test'],
        number: '999',
      }
      const wrapper = mount(VOvShare, {
        props: {
          share: ['twitter'],
          shareOptions,
        },
      })
      expect(wrapper.props('shareOptions')).toEqual(shareOptions)
    })

    it('passes useNativeBehavior to social media components', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['facebook', 'linkedin'],
          useNativeBehavior: true,
        },
      })
      expect(wrapper.props('useNativeBehavior')).toBe(true)
    })
  })

  describe('Data Attributes for Testing', () => {
    it('Twitter button has data-cy attribute', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['twitter'],
        },
      })
      const twitterBtn = wrapper.find('[data-cy="v-bsb-share-twitter"]')
      expect(twitterBtn.attributes('data-cy')).toBe('v-bsb-share-twitter')
    })

    it('Facebook button has data-cy attribute', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['facebook'],
        },
      })
      const facebookBtn = wrapper.find('[data-cy="v-bsb-share-facebook"]')
      expect(facebookBtn.attributes('data-cy')).toBe('v-bsb-share-facebook')
    })

    it('LinkedIn button has data-cy attribute', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['linkedin'],
        },
      })
      const linkedInBtn = wrapper.find('[data-cy="v-bsb-share-linkedin"]')
      expect(linkedInBtn.attributes('data-cy')).toBe('v-bsb-share-linkedin')
    })

    it('WhatsApp button has data-cy attribute', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['whatsapp'],
        },
      })
      const whatsappBtn = wrapper.find('[data-cy="v-bsb-share-whatsapp"]')
      expect(whatsappBtn.attributes('data-cy')).toBe('v-bsb-share-whatsapp')
    })

    it('Copy button has data-cy attribute', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['copy'],
        },
      })
      const copyBtn = wrapper.find('[data-cy="v-bsb-share-copy"]')
      expect(copyBtn.attributes('data-cy')).toBe('v-bsb-share-copy')
    })
  })

  describe('Mounting and Unmounting', () => {
    it('mounts and initializes correctly', async () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['twitter', 'facebook', 'linkedin', 'whatsapp', 'copy'],
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('can be unmounted without errors', async () => {
      const wrapper = mount(VOvShare)
      await wrapper.unmount()
      expect(wrapper).toBeDefined()
    })
  })

  describe('Props Updates', () => {
    it('updates share prop dynamically', async () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['twitter'],
        },
      })
      expect(wrapper.findComponent({ name: 'STwitter' }).exists()).toBe(true)
      expect(wrapper.findComponent({ name: 'SFacebook' }).exists()).toBe(false)

      await wrapper.setProps({ share: ['facebook'] })
      expect(wrapper.findComponent({ name: 'STwitter' }).exists()).toBe(false)
      expect(wrapper.findComponent({ name: 'SFacebook' }).exists()).toBe(true)
    })

    it('updates variant prop dynamically', async () => {
      const wrapper = mount(VOvShare, {
        props: {
          variant: 'flat',
        },
      })
      expect(wrapper.props('variant')).toBe('flat')

      await wrapper.setProps({ variant: 'outlined' })
      expect(wrapper.props('variant')).toBe('outlined')
    })

    it('updates density prop dynamically', async () => {
      const wrapper = mount(VOvShare, {
        props: {
          density: 'default',
        },
      })
      expect(wrapper.props('density')).toBe('default')

      await wrapper.setProps({ density: 'compact' })
      expect(wrapper.props('density')).toBe('compact')
    })

    it('updates color prop dynamically', async () => {
      const wrapper = mount(VOvShare, {
        props: {
          color: 'primary',
        },
      })
      expect(wrapper.props('color')).toBe('primary')

      await wrapper.setProps({ color: 'secondary' })
      expect(wrapper.props('color')).toBe('secondary')
    })

    it('updates shareOptions prop dynamically', async () => {
      const initialOptions = {
        text: 'Initial text',
        url: 'https://initial.com',
        number: '111',
      }
      const wrapper = mount(VOvShare, {
        props: {
          shareOptions: initialOptions,
        },
      })
      expect(wrapper.props('shareOptions')).toEqual(initialOptions)

      const newOptions = {
        text: 'New text',
        url: 'https://new.com',
        number: '222',
      }
      await wrapper.setProps({ shareOptions: newOptions })
      expect(wrapper.props('shareOptions')).toEqual(newOptions)
    })

    it('updates windowFeatures prop dynamically', async () => {
      const initialFeatures = { width: 600, height: 400, top: 100, left: 100 }
      const wrapper = mount(VOvShare, {
        props: {
          windowFeatures: initialFeatures,
        },
      })
      expect(wrapper.props('windowFeatures')).toEqual(initialFeatures)

      const newFeatures = { width: 800, height: 600, top: 50, left: 50 }
      await wrapper.setProps({ windowFeatures: newFeatures })
      expect(wrapper.props('windowFeatures')).toEqual(newFeatures)
    })
  })

  describe('Edge Cases', () => {
    it('handles props with undefined values gracefully', () => {
      const wrapper = mount(VOvShare, {
        props: {
          variant: undefined,
          density: undefined,
          color: undefined,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles empty text in shareOptions when copying', async () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['copy'],
          shareOptions: {
            url: 'https://example.com',
            number: '123',
          },
        },
      })
      const copyBtn = wrapper.find('[data-cy="v-bsb-share-copy"]')
      await copyBtn.trigger('click')
      await flushPromises()
      expect(navigator.clipboard.writeText).toHaveBeenCalledWith('')
    })

    it('handles multiple rapid copy clicks', async () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['copy'],
          shareOptions: {
            text: 'Test',
            url: 'https://example.com',
            number: '123',
          },
        },
      })
      const copyBtn = wrapper.find('[data-cy="v-bsb-share-copy"]')
      await copyBtn.trigger('click')
      await copyBtn.trigger('click')
      await copyBtn.trigger('click')
      await flushPromises()
      expect(navigator.clipboard.writeText).toHaveBeenCalledTimes(3)
    })

    it('handles share array with duplicate values', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['twitter', 'twitter', 'facebook'],
        },
      })
      const twitterCount = wrapper.findAllComponents({ name: 'STwitter' }).length
      expect(twitterCount).toBeGreaterThan(0)
    })
  })

  describe('Integration Tests', () => {
    it('renders complete share component with all default options', () => {
      const wrapper = mount(VOvShare)
      expect(wrapper.findComponent({ name: 'STwitter' }).exists()).toBe(true)
      expect(wrapper.findComponent({ name: 'SFacebook' }).exists()).toBe(true)
      expect(wrapper.findComponent({ name: 'SLinkedIn' }).exists()).toBe(true)
      expect(wrapper.findComponent({ name: 'SWhatsApp' }).exists()).toBe(true)
      expect(wrapper.find('[data-cy="v-bsb-share-copy"]').exists()).toBe(true)
    })

    it('renders custom configuration with specific social media and styling', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['twitter', 'facebook'],
          variant: 'outlined',
          density: 'compact',
          color: 'primary',
          shareOptions: {
            text: 'Check this out',
            url: 'https://example.com',
            number: '555',
          },
        },
      })
      expect(wrapper.findComponent({ name: 'STwitter' }).exists()).toBe(true)
      expect(wrapper.findComponent({ name: 'SFacebook' }).exists()).toBe(true)
      expect(wrapper.findComponent({ name: 'SLinkedIn' }).exists()).toBe(false)
      expect(wrapper.props('variant')).toBe('outlined')
      expect(wrapper.props('density')).toBe('compact')
      expect(wrapper.props('color')).toBe('primary')
    })

    it('supports minimal configuration', () => {
      const wrapper = mount(VOvShare, {
        props: {
          share: ['copy'],
        },
      })
      expect(wrapper.find('[data-cy="v-bsb-share-copy"]').exists()).toBe(true)
      expect(wrapper.findComponent({ name: 'STwitter' }).exists()).toBe(false)
    })
  })
})
VOvTable.test.ts
ts
import { describe, it, expect, vi, beforeAll } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import type { OvTableOptions, OvTableData } from '../index'
import VOvTable from '../VOvTable.vue'

// Mock ResizeObserver globally for all tests
beforeAll(() => {
  global.ResizeObserver = vi.fn().mockImplementation(() => ({
    observe: vi.fn(),
    disconnect: vi.fn(),
  })) as never
})

// Mock visualViewport for Vuetify VOverlay with proper event handling
if (typeof global.visualViewport === 'undefined') {
  const mockViewport: Partial<VisualViewport> & {
    addEventListener: ReturnType<typeof vi.fn>
    removeEventListener: ReturnType<typeof vi.fn>
  } = {
    width: 1024,
    height: 768,
    offsetLeft: 0,
    offsetTop: 0,
    pageLeft: 0,
    pageTop: 0,
    scale: 1,
    addEventListener: vi.fn(),
    removeEventListener: vi.fn(),
  }
  global.visualViewport = mockViewport as VisualViewport
}

describe('VOvTable', () => {
  const basicTableOptions: OvTableOptions = {
    key: 'id',
    columns: [
      { name: 'id', label: 'ID' },
      { name: 'name', label: 'Name' },
      { name: 'email', label: 'Email' },
    ],
  }

  const basicTableData: OvTableData[] = [
    { id: 1, name: 'John', email: 'john@example.com' },
    { id: 2, name: 'Jane', email: 'jane@example.com' },
    { id: 3, name: 'Bob', email: 'bob@example.com' },
  ]

  describe('Rendering', () => {
    it('mounts without error', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('renders VContainer component', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      await flushPromises()
      const container = wrapper.findComponent({ name: 'VContainer' })
      expect(container.exists()).toBe(true)
    })

    it('renders VTable component', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      await flushPromises()
      const table = wrapper.findComponent({ name: 'VTable' })
      expect(table.exists()).toBe(true)
    })

    it('renders table with correct columns', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('renders table rows with data', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('renders pagination buttons', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      await flushPromises()
      const buttons = wrapper.findAllComponents({ name: 'VBtn' })
      expect(buttons.length).toBeGreaterThan(0)
    })

    it('renders loading overlay when loading is true', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
          loading: true,
        },
      })
      await flushPromises()
      const overlay = wrapper.findComponent({ name: 'VOverlay' })
      expect(overlay.exists()).toBe(true)
    })
  })

  describe('Props - Table Options', () => {
    it('accepts options prop with key and columns', () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      expect(wrapper.props('options')).toEqual(basicTableOptions)
    })

    it('accepts options with search configuration', async () => {
      const optionsWithSearch: OvTableOptions = {
        ...basicTableOptions,
        search: {
          label: 'Search',
          placeholder: 'Enter search term',
          value: '',
        },
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithSearch,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.props('options').search).toBeDefined()
    })

    it('accepts options with filter configuration', async () => {
      const optionsWithFilter: OvTableOptions = {
        ...basicTableOptions,
        filter: {
          fields: [{ type: 'text', name: 'name', label: 'Name' }],
        },
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithFilter,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.props('options').filter).toBeDefined()
    })

    it('accepts options with sort configuration', async () => {
      const optionsWithSort: OvTableOptions = {
        ...basicTableOptions,
        sort: [
          { name: 'name', label: 'Name' },
          { name: 'email', label: 'Email' },
        ],
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithSort,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.props('options').sort).toBeDefined()
    })

    it('accepts options with itemsPerPage', async () => {
      const optionsWithPagination: OvTableOptions = {
        ...basicTableOptions,
        itemsPerPage: 5,
        currentPage: 1,
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithPagination,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.props('options').itemsPerPage).toBe(5)
    })

    it('accepts options with column actions', async () => {
      const optionsWithActions: OvTableOptions = {
        ...basicTableOptions,
        columns: [
          { name: 'id', label: 'ID' },
          { name: 'name', label: 'Name' },
          {
            name: 'actions',
            label: 'Actions',
            actions: ['edit', 'delete'],
          },
        ],
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithActions,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('accepts options with table-level actions', async () => {
      const optionsWithTableActions: OvTableOptions = {
        ...basicTableOptions,
        actions: ['add', 'export'],
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithTableActions,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.props('options').actions).toBeDefined()
    })

    it('defaults canRefresh to true', () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      expect(wrapper.props('options').canRefresh).toBeUndefined()
    })
  })

  describe('Props - Data', () => {
    it('accepts data prop as array of table data', () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      expect(wrapper.props('data')).toEqual(basicTableData)
    })

    it('handles empty data array', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: [],
        },
      })
      await flushPromises()
      expect(wrapper.props('data')).toEqual([])
    })

    it('defaults data to empty array if not provided', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
        },
      })
      await flushPromises()
      // Component defaults to empty array when data is not provided
      const data = wrapper.props('data')
      expect(Array.isArray(data) || data === undefined).toBe(true)
    })

    it('updates when data prop changes', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      await flushPromises()

      const newData = [
        { id: 4, name: 'Alice', email: 'alice@example.com' },
        { id: 5, name: 'Charlie', email: 'charlie@example.com' },
      ]
      await wrapper.setProps({ data: newData })
      await flushPromises()
      expect(wrapper.props('data')).toEqual(newData)
    })

    it('handles data with multiple rows', async () => {
      const manyRows = Array.from({ length: 100 }, (_, i) => ({
        id: i + 1,
        name: `User ${i + 1}`,
        email: `user${i + 1}@example.com`,
      }))
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: manyRows,
        },
      })
      await flushPromises()
      expect(wrapper.props('data')).toHaveLength(100)
    })
  })

  describe('Props - Loading', () => {
    it('accepts loading prop as false', () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
          loading: false,
        },
      })
      expect(wrapper.props('loading')).toBe(false)
    })

    it('accepts loading prop as true', () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
          loading: true,
        },
      })
      expect(wrapper.props('loading')).toBe(true)
    })

    it('defaults loading to false', () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      expect(wrapper.props('loading')).toBe(false)
    })
  })

  describe('Table Columns', () => {
    it('renders all columns from options', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('displays column titles from options', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('applies column alignment', async () => {
      const optionsWithAlign: OvTableOptions = {
        ...basicTableOptions,
        columns: [
          { name: 'id', label: 'ID', align: 'center' },
          { name: 'name', label: 'Name', align: 'left' },
          { name: 'email', label: 'Email', align: 'right' },
        ],
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithAlign,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('applies format to column values', async () => {
      const optionsWithFormat: OvTableOptions = {
        ...basicTableOptions,
        columns: [
          { name: 'id', label: 'ID' },
          { name: 'name', label: 'Name' },
          {
            name: 'email',
            label: 'Email',
            format: { icon: '$mdiEmail', color: 'primary' },
          },
        ],
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithFormat,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('respects maxLength on columns', async () => {
      const optionsWithMaxLength: OvTableOptions = {
        ...basicTableOptions,
        columns: [
          { name: 'id', label: 'ID' },
          { name: 'name', label: 'Name', maxLength: 5 },
          { name: 'email', label: 'Email' },
        ],
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithMaxLength,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Table Actions', () => {
    it('renders table-level action buttons', async () => {
      const optionsWithActions: OvTableOptions = {
        ...basicTableOptions,
        actions: ['add', 'delete'],
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithActions,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('emits action event when table action is clicked', async () => {
      const optionsWithActions: OvTableOptions = {
        ...basicTableOptions,
        actions: ['add'],
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithActions,
          data: basicTableData,
        },
      })
      await flushPromises()

      const buttons = wrapper.findAllComponents({ name: 'VBtn' })
      expect(buttons.length).toBeGreaterThan(0)
      await buttons[0]!.trigger('click')
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('renders column-level action buttons', async () => {
      const optionsWithColumnActions: OvTableOptions = {
        ...basicTableOptions,
        columns: [
          { name: 'id', label: 'ID' },
          { name: 'name', label: 'Name' },
          {
            name: 'actions',
            label: 'Actions',
            actions: ['edit', 'delete'],
          },
        ],
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithColumnActions,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Table Search', () => {
    it('renders search input when search option is provided', async () => {
      const optionsWithSearch: OvTableOptions = {
        ...basicTableOptions,
        search: { label: 'Search', placeholder: 'Type here...' },
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithSearch,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('does not render search input when search option is not provided', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('emits fetch event on search submission', async () => {
      const optionsWithSearch: OvTableOptions = {
        ...basicTableOptions,
        search: { label: 'Search' },
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithSearch,
          data: basicTableData,
        },
      })
      await flushPromises()

      // Verify fetch event is emitted
      const emitted = wrapper.emitted('fetch')
      expect(emitted).toBeDefined()
    })
  })

  describe('Table Filter', () => {
    it('renders filter button when filter option is provided', async () => {
      const optionsWithFilter: OvTableOptions = {
        ...basicTableOptions,
        filter: {
          fields: [{ type: 'text', name: 'name', label: 'Name' }],
        },
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithFilter,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('renders filter chips when filters are active', async () => {
      const optionsWithFilter: OvTableOptions = {
        ...basicTableOptions,
        filter: {
          fields: [
            {
              type: 'text',
              name: 'name',
              label: 'Name',
              value: 'John',
            },
          ],
        },
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithFilter,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('emits fetch event when filter changes', async () => {
      const optionsWithFilter: OvTableOptions = {
        ...basicTableOptions,
        filter: {
          fields: [{ type: 'text', name: 'name', label: 'Name' }],
        },
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithFilter,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Table Sort', () => {
    it('renders sort button when sort option is provided', async () => {
      const optionsWithSort: OvTableOptions = {
        ...basicTableOptions,
        sort: [
          { name: 'name', label: 'Name' },
          { name: 'email', label: 'Email' },
        ],
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithSort,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('renders sort chips when sorts are active', async () => {
      const optionsWithSort: OvTableOptions = {
        ...basicTableOptions,
        sort: [{ name: 'name', label: 'Name', value: 'asc' }],
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithSort,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('emits fetch event when sort changes', async () => {
      const optionsWithSort: OvTableOptions = {
        ...basicTableOptions,
        sort: [{ name: 'name', label: 'Name' }],
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithSort,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Pagination', () => {
    it('renders next/prev pagination buttons', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      await flushPromises()
      const buttons = wrapper.findAllComponents({ name: 'VBtn' })
      expect(buttons.length).toBeGreaterThan(0)
    })

    it('disables prev button on first page', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: {
            ...basicTableOptions,
            itemsPerPage: 2,
            currentPage: 1,
          },
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('disables next button on last page', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: {
            ...basicTableOptions,
            itemsPerPage: 10,
            currentPage: 1,
          },
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('respects itemsPerPage option', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: {
            ...basicTableOptions,
            itemsPerPage: 5,
          },
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.props('options').itemsPerPage).toBe(5)
    })

    it('emits fetch event on page change', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: Array.from({ length: 25 }, (_, i) => ({
            id: i + 1,
            name: `User ${i + 1}`,
            email: `user${i + 1}@example.com`,
          })),
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Refresh Button', () => {
    it('renders refresh button when canRefresh is true', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: {
            ...basicTableOptions,
            canRefresh: true,
          },
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('hides refresh button when canRefresh is false', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: {
            ...basicTableOptions,
            canRefresh: false,
          },
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('emits fetch event on refresh', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: {
            ...basicTableOptions,
            canRefresh: true,
          },
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Form Dialog', () => {
    it('shows form dialog when row action with form is triggered', async () => {
      const optionsWithForm: OvTableOptions = {
        ...basicTableOptions,
        columns: [
          { name: 'id', label: 'ID' },
          {
            name: 'name',
            label: 'Name',
            actions: [
              {
                name: 'edit',
                form: {
                  fields: [{ type: 'text', name: 'name', label: 'Name' }],
                },
              },
            ],
          },
        ],
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithForm,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('shows form dialog for table action with form', async () => {
      const optionsWithTableForm: OvTableOptions = {
        ...basicTableOptions,
        actions: [
          {
            name: 'add',
            form: {
              fields: [{ type: 'text', name: 'name', label: 'Name' }],
            },
          },
        ],
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithTableForm,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('emits action event on form submission', async () => {
      const optionsWithForm: OvTableOptions = {
        ...basicTableOptions,
        actions: [
          {
            name: 'add',
            form: {
              fields: [{ type: 'text', name: 'name', label: 'Name' }],
              actions: ['submit', 'cancel'],
              actionSubmit: 'submit',
              actionCancel: 'cancel',
            },
          },
        ],
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithForm,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Emitted Events', () => {
    it('emits fetch event on mount', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      await flushPromises()
      const emitted = wrapper.emitted('fetch')
      expect(emitted).toBeDefined()
      expect(emitted?.length).toBeGreaterThan(0)
    })

    it('emits action event with correct parameters', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('fetch event includes pagination parameters', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: {
            ...basicTableOptions,
            itemsPerPage: 10,
          },
          data: basicTableData,
        },
      })
      await flushPromises()
      const emitted = wrapper.emitted('fetch')
      expect(emitted).toBeDefined()
    })

    it('fetch event includes search parameters when search is enabled', async () => {
      const optionsWithSearch: OvTableOptions = {
        ...basicTableOptions,
        search: { label: 'Search' },
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithSearch,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('fetch event includes filter parameters when filter is enabled', async () => {
      const optionsWithFilter: OvTableOptions = {
        ...basicTableOptions,
        filter: {
          fields: [{ type: 'text', name: 'name', label: 'Name' }],
        },
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithFilter,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('fetch event includes sort parameters when sort is enabled', async () => {
      const optionsWithSort: OvTableOptions = {
        ...basicTableOptions,
        sort: [{ name: 'name', label: 'Name' }],
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: optionsWithSort,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Exposed Methods', () => {
    it('exposes fetch method', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(typeof wrapper.vm.fetch).toBe('function')
    })

    it('fetch method can be called without parameters', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      await flushPromises()
      await wrapper.vm.fetch()
      expect(wrapper.vm).toBeDefined()
    })

    it('fetch method can be called with page number', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      await flushPromises()
      await wrapper.vm.fetch(2)
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Responsive Layout', () => {
    it('renders table with default desktop layout', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('handles data with different object structures', async () => {
      const complexData: OvTableData[] = [
        { id: 1, name: 'John', email: 'john@example.com', nested: { value: 'test' } },
        { id: 2, name: 'Jane', email: 'jane@example.com', nested: { value: 'test2' } },
      ]
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: complexData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Edge Cases', () => {
    it('handles empty options columns array', async () => {
      const emptyColumnsOptions: OvTableOptions = {
        key: 'id',
        columns: [],
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: emptyColumnsOptions,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('handles data without key field', async () => {
      const dataWithoutKey = [
        { name: 'John', email: 'john@example.com' },
        { name: 'Jane', email: 'jane@example.com' },
      ]
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: dataWithoutKey,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('handles very long text values', async () => {
      const longTextData: OvTableData[] = [
        {
          id: 1,
          name: 'John ' + 'x'.repeat(1000),
          email: 'john@example.com',
        },
      ]
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: longTextData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('handles special characters in data', async () => {
      const specialCharData: OvTableData[] = [
        { id: 1, name: 'John <script>', email: 'john&jane@example.com' },
        { id: 2, name: 'Jane "quotes"', email: 'jane@example.com' },
      ]
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: specialCharData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('handles null and undefined values in data', async () => {
      const nullData: OvTableData[] = [
        { id: 1, name: null, email: 'john@example.com' },
        { id: 2, name: undefined, email: 'jane@example.com' },
      ]
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: nullData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('handles numeric data in cells', async () => {
      const numericData: OvTableData[] = [
        { id: 1, name: 123, email: 'john@example.com' },
        { id: 2, name: 45.67, email: 'jane@example.com' },
      ]
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: numericData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Complex Scenarios', () => {
    it('handles table with all features enabled', async () => {
      const fullFeaturedOptions: OvTableOptions = {
        key: 'id',
        columns: [
          { name: 'id', label: 'ID', align: 'center' },
          { name: 'name', label: 'Name' },
          { name: 'email', label: 'Email', format: { icon: '$mdiEmail' } },
          {
            name: 'actions',
            label: 'Actions',
            actions: ['edit', 'delete'],
          },
        ],
        search: { label: 'Search', placeholder: 'Search...' },
        filter: {
          fields: [{ type: 'text', name: 'name', label: 'Name' }],
        },
        sort: [
          { name: 'name', label: 'Name' },
          { name: 'email', label: 'Email' },
        ],
        actions: ['add', 'export'],
        itemsPerPage: 10,
        currentPage: 1,
        canRefresh: true,
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: fullFeaturedOptions,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('handles table with multiple column formats', async () => {
      const multiFormatOptions: OvTableOptions = {
        key: 'id',
        columns: [
          { name: 'id', label: 'ID' },
          {
            name: 'status',
            label: 'Status',
            format: [
              { color: 'green', text: 'Active', rules: { type: 'equals', params: 'active' } },
              { color: 'red', text: 'Inactive', rules: { type: 'equals', params: 'inactive' } },
            ],
          },
          { name: 'email', label: 'Email' },
        ],
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: multiFormatOptions,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })

    it('handles complex form submission in table context', async () => {
      const formOptions: OvTableOptions = {
        ...basicTableOptions,
        actions: [
          {
            name: 'add',
            form: {
              fields: [
                { type: 'text', name: 'name', label: 'Name', required: true },
                { type: 'email', name: 'email', label: 'Email', required: true },
              ],
              actions: ['submit', 'cancel'],
              actionSubmit: 'submit',
              actionCancel: 'cancel',
            },
          },
        ],
      }
      const wrapper = mount(VOvTable, {
        props: {
          options: formOptions,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Mounting and Unmounting', () => {
    it('mounts and initializes correctly', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      await flushPromises()
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('can be unmounted without errors', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      await wrapper.unmount()
      expect(wrapper).toBeDefined()
    })
  })

  describe('Props Updates', () => {
    it('updates when options prop changes', async () => {
      const initialOptions = basicTableOptions
      const wrapper = mount(VOvTable, {
        props: {
          options: initialOptions,
          data: basicTableData,
        },
      })
      await flushPromises()

      const newOptions: OvTableOptions = {
        key: 'id',
        columns: [
          { name: 'id', label: 'ID' },
          { name: 'name', label: 'Name' },
          { name: 'email', label: 'Email' },
          { name: 'phone', label: 'Phone' },
        ],
      }
      await wrapper.setProps({ options: newOptions })
      await flushPromises()
      expect(wrapper.props('options').columns.length).toBe(4)
    })

    it('updates when data prop changes', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
        },
      })
      await flushPromises()

      const newData: OvTableData[] = [{ id: 10, name: 'Alice', email: 'alice@example.com' }]
      await wrapper.setProps({ data: newData })
      await flushPromises()
      expect(wrapper.props('data')).toEqual(newData)
    })

    it('updates loading state', async () => {
      const wrapper = mount(VOvTable, {
        props: {
          options: basicTableOptions,
          data: basicTableData,
          loading: false,
        },
      })
      expect(wrapper.props('loading')).toBe(false)

      await wrapper.setProps({ loading: true })
      expect(wrapper.props('loading')).toBe(true)
    })
  })
})
VOvView.test.ts
ts
import { describe, it, expect, vi } from 'vitest'
import { mount, flushPromises } from '@vue/test-utils'
import type { OvViewOptions, OvViewData } from '../index'
import VOvView from '../VOvView.vue'

// Mock visualViewport for Vuetify VOverlay with proper event handling
if (typeof global.visualViewport === 'undefined') {
  const mockViewport: Partial<VisualViewport> & {
    addEventListener: ReturnType<typeof vi.fn>
    removeEventListener: ReturnType<typeof vi.fn>
  } = {
    width: 1024,
    height: 768,
    offsetLeft: 0,
    offsetTop: 0,
    pageLeft: 0,
    pageTop: 0,
    scale: 1,
    addEventListener: vi.fn(),
    removeEventListener: vi.fn(),
  }
  global.visualViewport = mockViewport as VisualViewport
}

// Mock ResizeObserver for VProgressCircular
if (typeof global.ResizeObserver === 'undefined') {
  global.ResizeObserver = vi.fn().mockImplementation(() => ({
    observe: vi.fn(),
    unobserve: vi.fn(),
    disconnect: vi.fn(),
  }))
}

describe('VOvView', () => {
  const basicViewOptions: OvViewOptions = {
    items: [
      { name: 'field1', label: 'Field 1' },
      { name: 'field2', label: 'Field 2' },
    ],
  }

  const basicViewData: OvViewData = {
    field1: 'Value 1',
    field2: 'Value 2',
  }

  describe('Rendering', () => {
    it('mounts without error', () => {
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: basicViewOptions,
        },
      })
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('renders VContainer when multiple items exist', () => {
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: basicViewOptions,
        },
      })
      const container = wrapper.findComponent({ name: 'VContainer' })
      expect(container.exists()).toBe(true)
    })

    it('renders single item directly without VContainer', () => {
      const singleItemOptions: OvViewOptions = {
        items: [{ name: 'field1', label: 'Field 1' }],
      }
      const wrapper = mount(VOvView, {
        props: {
          data: { field1: 'Value 1' },
          options: singleItemOptions,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('renders VRow for items', () => {
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: basicViewOptions,
        },
      })
      const rows = wrapper.findAllComponents({ name: 'VRow' })
      expect(rows.length).toBeGreaterThan(0)
    })

    it('renders VCol for each item', () => {
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: basicViewOptions,
        },
      })
      const cols = wrapper.findAllComponents({ name: 'VCol' })
      expect(cols.length).toBeGreaterThanOrEqual(2)
    })

    it('renders action buttons when actions are provided', () => {
      const optionsWithActions: OvViewOptions = {
        items: [
          { name: 'field1', label: 'Field 1' },
          { name: 'field2', label: 'Field 2' },
        ],
        actions: ['edit', 'delete'],
      }
      const wrapper = mount(VOvView, {
        props: {
          data: { field1: 'Value 1', field2: 'Value 2' },
          options: optionsWithActions,
        },
      })
      const buttons = wrapper.findAllComponents({ name: 'VBtn' })
      expect(buttons.length).toBeGreaterThan(0)
    })

    it('does not render action buttons when actions are empty', () => {
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: { ...basicViewOptions, actions: [] },
        },
      })
      const buttons = wrapper.findAllComponents({ name: 'VBtn' })
      expect(buttons.length).toBe(0)
    })
  })

  describe('Props - Data', () => {
    it('accepts data prop', () => {
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: basicViewOptions,
        },
      })
      expect(wrapper.props('data')).toEqual(basicViewData)
    })

    it('handles empty data object', () => {
      const wrapper = mount(VOvView, {
        props: {
          data: {},
          options: basicViewOptions,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles undefined values in data', () => {
      const wrapper = mount(VOvView, {
        props: {
          data: {
            field1: undefined,
            field2: 'Value 2',
          },
          options: basicViewOptions,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('updates view when data prop changes', async () => {
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: basicViewOptions,
        },
      })
      const newData = { field1: 'Updated 1', field2: 'Updated 2' }
      await wrapper.setProps({ data: newData })
      await flushPromises()
      expect(wrapper.props('data')).toEqual(newData)
    })
  })

  describe('Props - Options', () => {
    it('accepts options prop', () => {
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: basicViewOptions,
        },
      })
      expect(wrapper.props('options')).toEqual(basicViewOptions)
    })

    it('renders items from options', () => {
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: basicViewOptions,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies cols option for grid layout', () => {
      const optionsWithCols: OvViewOptions = {
        items: basicViewOptions.items,
        cols: 2,
      }
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: optionsWithCols,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('defaults to 1 column when cols is not specified', () => {
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: basicViewOptions,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Props - Translation Function', () => {
    it('accepts translation function prop', () => {
      const mockT = (text?: string) => `translated_${text}`
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: basicViewOptions,
          t: mockT,
        },
      })
      expect(wrapper.props('t')).toBe(mockT)
    })

    it('defaults translation function if not provided', () => {
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: basicViewOptions,
        },
      })
      const defaultT = wrapper.props('t')
      expect(typeof defaultT).toBe('function')
    })

    it('uses translation function for button text', () => {
      const mockT = (text?: string) => {
        const translations: Record<string, string> = {
          edit: 'Editar',
          delete: 'Eliminar',
        }
        return translations[text || ''] || text || ''
      }
      const optionsWithActions: OvViewOptions = {
        items: [{ name: 'field1', label: 'Field 1' }],
        actions: ['edit'],
      }
      const wrapper = mount(VOvView, {
        props: {
          data: { field1: 'Value' },
          options: optionsWithActions,
          t: mockT,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Props - Loading', () => {
    it('accepts loading prop', () => {
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: basicViewOptions,
          loading: false,
        },
      })
      expect(wrapper.props('loading')).toBe(false)
    })

    it('defaults loading to false', () => {
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: basicViewOptions,
        },
      })
      expect(wrapper.props('loading')).toBe(false)
    })

    it('renders progress circular when loading is true', () => {
      const optionsWithItems: OvViewOptions = {
        items: [
          { name: 'field1', label: 'Field 1' },
          { name: 'field2', label: 'Field 2' },
        ],
      }
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: optionsWithItems,
          loading: true,
        },
      })
      const progress = wrapper.findComponent({ name: 'VProgressCircular' })
      expect(progress.exists()).toBe(true)
    })

    it('shows overlay when loading is true', () => {
      const optionsWithItems: OvViewOptions = {
        items: [
          { name: 'field1', label: 'Field 1' },
          { name: 'field2', label: 'Field 2' },
        ],
      }
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: optionsWithItems,
          loading: true,
        },
      })
      const overlay = wrapper.findComponent({ name: 'VOverlay' })
      expect(overlay.exists()).toBe(true)
      // Verify the overlay is visible by checking if its modelValue is true
      expect(wrapper.vm.loading).toBe(true)
    })
  })

  describe('View Items', () => {
    it('renders view items with labels', () => {
      const optionsWithLabels: OvViewOptions = {
        items: [
          { name: 'field1', label: 'User Name' },
          { name: 'field2', label: 'Email Address' },
        ],
      }
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: optionsWithLabels,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles view items without labels', () => {
      const optionsNoLabels: OvViewOptions = {
        items: [{ name: 'field1' }, { name: 'field2' }],
      }
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: optionsNoLabels,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies format to view items', () => {
      const optionsWithFormat: OvViewOptions = {
        items: [
          { name: 'field1', label: 'Field 1', format: { color: 'primary', icon: '$mdiAccount' } },
        ],
      }
      const wrapper = mount(VOvView, {
        props: {
          data: { field1: 'Value 1' },
          options: optionsWithFormat,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('renders item actions', () => {
      const optionsWithItemActions: OvViewOptions = {
        items: [{ name: 'field1', label: 'Field 1', actions: ['view', 'edit'] }],
      }
      const wrapper = mount(VOvView, {
        props: {
          data: { field1: 'Value 1' },
          options: optionsWithItemActions,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies maxLength to item values', () => {
      const optionsWithMaxLength: OvViewOptions = {
        items: [{ name: 'field1', label: 'Field 1', maxLength: 10 }],
      }
      const wrapper = mount(VOvView, {
        props: {
          data: { field1: 'This is a very long value that should be truncated' },
          options: optionsWithMaxLength,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Actions', () => {
    it('renders action buttons with action names', () => {
      const optionsWithActions: OvViewOptions = {
        items: [{ name: 'field1', label: 'Field 1' }],
        actions: ['edit', 'delete'],
      }
      const wrapper = mount(VOvView, {
        props: {
          data: { field1: 'Value 1' },
          options: optionsWithActions,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('renders action buttons with format', () => {
      const optionsWithFormattedActions: OvViewOptions = {
        items: [{ name: 'field1', label: 'Field 1' }],
        actions: [
          { name: 'edit', format: { color: 'primary', icon: '$mdiPencil' } },
          { name: 'delete', format: { color: 'error', icon: '$mdiDelete' } },
        ],
      }
      const wrapper = mount(VOvView, {
        props: {
          data: { field1: 'Value 1' },
          options: optionsWithFormattedActions,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies actionAlign to action buttons', () => {
      const optionsWithActionAlign: OvViewOptions = {
        items: [{ name: 'field1', label: 'Field 1' }],
        actions: ['edit'],
        actionAlign: 'center',
      }
      const wrapper = mount(VOvView, {
        props: {
          data: { field1: 'Value 1' },
          options: optionsWithActionAlign,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('applies actionFormat to all actions', () => {
      const optionsWithActionFormat: OvViewOptions = {
        items: [{ name: 'field1', label: 'Field 1' }],
        actions: ['edit', 'delete'],
        actionFormat: { variant: 'outlined', density: 'compact' },
      }
      const wrapper = mount(VOvView, {
        props: {
          data: { field1: 'Value 1' },
          options: optionsWithActionFormat,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Events', () => {
    it('emits action event on button click', async () => {
      const optionsWithActions: OvViewOptions = {
        items: [{ name: 'field1', label: 'Field 1' }],
        actions: ['edit'],
      }
      const wrapper = mount(VOvView, {
        props: {
          data: { field1: 'Value 1' },
          options: optionsWithActions,
        },
      })
      await wrapper.vm.$nextTick()
      wrapper.vm.$emit('action', 'edit', 'Value 1')
      expect(wrapper.emitted('action')).toBeTruthy()
    })

    it('emits details event for truncated content', async () => {
      const optionsWithMaxLength: OvViewOptions = {
        items: [{ name: 'field1', label: 'Field 1', maxLength: 5 }],
      }
      const wrapper = mount(VOvView, {
        props: {
          data: { field1: 'This is a long value' },
          options: optionsWithMaxLength,
        },
      })
      await wrapper.vm.$nextTick()
      wrapper.vm.$emit('details', 'Field 1', 'This is a long value')
      expect(wrapper.emitted('details')).toBeTruthy()
    })
  })

  describe('Responsive Layout', () => {
    it('adapts columns for mobile display', () => {
      const optionsWithCols: OvViewOptions = {
        items: [
          { name: 'field1', label: 'Field 1' },
          { name: 'field2', label: 'Field 2' },
        ],
        cols: 2,
      }
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: optionsWithCols,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Props Updates', () => {
    it('updates view when options prop changes', async () => {
      const initialOptions: OvViewOptions = {
        items: [{ name: 'field1', label: 'Field 1' }],
      }
      const wrapper = mount(VOvView, {
        props: {
          data: { field1: 'Value 1' },
          options: initialOptions,
        },
      })

      const newOptions: OvViewOptions = {
        items: [
          { name: 'field1', label: 'Field 1' },
          { name: 'field2', label: 'Field 2' },
        ],
      }
      await wrapper.setProps({
        options: newOptions,
        data: { field1: 'Value 1', field2: 'Value 2' },
      })
      await flushPromises()
      expect(wrapper.props('options')!.items.length).toBe(2)
    })

    it('updates loading state', async () => {
      const optionsWithItems: OvViewOptions = {
        items: [
          { name: 'field1', label: 'Field 1' },
          { name: 'field2', label: 'Field 2' },
        ],
      }
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: optionsWithItems,
          loading: false,
        },
      })
      expect(wrapper.props('loading')).toBe(false)
      await wrapper.setProps({ loading: true })
      await wrapper.vm.$nextTick()
      expect(wrapper.props('loading')).toBe(true)
    })
  })

  describe('Complex Scenarios', () => {
    it('renders view with all features combined', async () => {
      const complexOptions: OvViewOptions = {
        items: [
          { name: 'username', label: 'Username', format: { color: 'primary' } },
          { name: 'email', label: 'Email', format: { icon: '$mdiEmail' } },
          { name: 'role', label: 'Role', maxLength: 20 },
        ],
        actions: [
          { name: 'edit', format: { color: 'primary', icon: '$mdiPencil' } },
          { name: 'delete', format: { color: 'error', icon: '$mdiDelete' } },
        ],
        actionAlign: 'center',
        actionFormat: { variant: 'outlined' },
        cols: 2,
      }
      const complexData: OvViewData = {
        username: 'john_doe',
        email: 'john@example.com',
        role: 'Administrator',
      }
      const wrapper = mount(VOvView, {
        props: {
          data: complexData,
          options: complexOptions,
          loading: false,
        },
      })
      await wrapper.vm.$nextTick()
      expect(wrapper.vm).toBeDefined()
      expect(wrapper.exists()).toBe(true)
    })

    it('renders single item view without container', () => {
      const singleItemOptions: OvViewOptions = {
        items: [{ name: 'status', label: 'Status', format: { color: 'success' } }],
      }
      const wrapper = mount(VOvView, {
        props: {
          data: { status: 'Active' },
          options: singleItemOptions,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })
  })

  describe('Edge Cases', () => {
    it('handles view with no items', () => {
      const emptyOptions: OvViewOptions = {
        items: [],
      }
      const wrapper = mount(VOvView, {
        props: {
          data: {},
          options: emptyOptions,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles view with missing data values', () => {
      const wrapper = mount(VOvView, {
        props: {
          data: { field1: 'Value 1' },
          options: {
            items: [
              { name: 'field1', label: 'Field 1' },
              { name: 'field2', label: 'Field 2' },
            ],
          },
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles view with null values in data', () => {
      const wrapper = mount(VOvView, {
        props: {
          data: { field1: null, field2: 'Value 2' },
          options: basicViewOptions,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles view with special characters in values', () => {
      const wrapper = mount(VOvView, {
        props: {
          data: {
            field1: 'Value with <special> & "characters"',
            field2: 'Value with "quotes" and \'apostrophes\'',
          },
          options: basicViewOptions,
        },
      })
      expect(wrapper.vm).toBeDefined()
    })

    it('handles mounting and unmounting', async () => {
      const wrapper = mount(VOvView, {
        props: {
          data: basicViewData,
          options: basicViewOptions,
        },
      })
      expect(wrapper.exists()).toBe(true)
      await wrapper.unmount()
      expect(wrapper.vm).toBeDefined()
    })
  })
})

Composables ​

http.test.ts
ts
import { describe, it, expect, vi, beforeEach } from 'vitest'

vi.mock('ofetch', () => {
  const mockCreate = vi.fn((options) => ({
    ...options,
    __isMocked: true,
  }))
  return {
    $fetch: {
      create: mockCreate,
    },
  }
})

import { useHttp } from '../http'
import { $fetch } from 'ofetch'

describe('useHttp', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  it('returns a fetch instance', () => {
    const http = useHttp()
    expect(http).toBeDefined()
    expect(typeof http).toBe('function')
    expect(http.get).toBeDefined()
    expect(http.post).toBeDefined()
    expect(http.put).toBeDefined()
    expect(http.delete).toBeDefined()
    expect(http.patch).toBeDefined()
  })

  it('creates instance with baseURL configuration', () => {
    vi.mocked($fetch.create).mockClear()
    useHttp()
    expect(vi.mocked($fetch.create)).toHaveBeenCalledOnce()
    const callArgs = vi.mocked($fetch.create).mock.calls[0]?.[0]
    expect(callArgs).toHaveProperty('baseURL')
  })

  it('returns same type instance on multiple calls', () => {
    const http1 = useHttp()
    const http2 = useHttp()
    expect(typeof http1).toBe(typeof http2)
  })
})
ui.test.ts
ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { useCardBackground } from '../ui'

// Mock vuetify useTheme
const mockTheme = {
  current: {
    value: {
      dark: false,
    },
  },
}

vi.mock('vuetify', () => ({
  useTheme: vi.fn(() => mockTheme),
}))

describe('useCardBackground', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  it('returns a computed object with background property', () => {
    const result = useCardBackground('#FF0000')
    expect(result).toBeDefined()
    expect(result.value).toBeDefined()
    expect(result.value.background).toBeDefined()
  })

  it('generates gradient background for light theme', () => {
    mockTheme.current.value.dark = false
    const result = useCardBackground('#FF0000')
    const background = result.value.background
    expect(background).toContain('linear-gradient')
    expect(background).toContain('#FF000066')
    expect(background).toContain('#FF000033')
  })

  it('generates gradient background for dark theme', () => {
    mockTheme.current.value.dark = true
    const result = useCardBackground('#FF0000')
    const background = result.value.background
    expect(background).toContain('linear-gradient')
    expect(background).toContain('#FF000033')
    expect(background).toContain('#FF000066')
  })

  it('accepts different color values', () => {
    mockTheme.current.value.dark = false
    const colors = ['#FF0000', '#00FF00', '#0000FF', '#FFA500']
    colors.forEach((color) => {
      const result = useCardBackground(color)
      expect(result.value.background).toContain(color)
    })
  })

  it('responds to theme changes reactively', () => {
    mockTheme.current.value.dark = false
    const result = useCardBackground('#FF0000')
    const lightBackground = result.value.background

    mockTheme.current.value.dark = true
    // Computed is reactive - accessing .value after theme change should reflect new theme
    const darkBackground = result.value.background

    expect(lightBackground).toContain('#FF000066')
    expect(darkBackground).toContain('#FF000033')
  })

  it('includes 135deg angle in gradient', () => {
    const result = useCardBackground('#FF0000')
    expect(result.value.background).toContain('135deg')
  })

  it('returns consistent output for same input', () => {
    const result1 = useCardBackground('#FF0000')
    const result2 = useCardBackground('#FF0000')
    expect(result1.value.background).toBe(result2.value.background)
  })
})