File size: 2,130 Bytes
004f460
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { renderHook, act, waitFor } from '@testing-library/react'
import { useCamera } from './useCamera'

function fakeStream() {
  const tracks = [
    { stop: vi.fn(), kind: 'video', getSettings: () => ({ deviceId: 'cam1' }) },
    { stop: vi.fn(), kind: 'video', getSettings: () => ({ deviceId: 'cam1' }) },
  ]
  return {
    _tracks: tracks,
    getTracks: () => tracks,
    getVideoTracks: () => tracks,
  } as unknown as MediaStream & { _tracks: { stop: ReturnType<typeof vi.fn> }[] }
}

describe('useCamera — track lifecycle', () => {
  let stream: ReturnType<typeof fakeStream>
  beforeEach(() => {
    stream = fakeStream()
    Object.defineProperty(navigator, 'mediaDevices', {
      configurable: true,
      value: { getUserMedia: vi.fn().mockResolvedValue(stream), enumerateDevices: vi.fn().mockResolvedValue([]) },
    })
  })
  afterEach(() => vi.restoreAllMocks())

  it('start() acquires a stream and stop() stops ALL tracks', async () => {
    const { result } = renderHook(() => useCamera())
    await act(async () => { await result.current.start() })
    await waitFor(() => expect(result.current.status).toBe('live'))
    act(() => result.current.stop())
    for (const tr of stream._tracks) expect(tr.stop).toHaveBeenCalled()
    expect(result.current.status).toBe('idle')
  })

  it('maps NotAllowedError to denied', async () => {
    const gum = navigator.mediaDevices.getUserMedia as ReturnType<typeof vi.fn>
    gum.mockRejectedValueOnce(Object.assign(new Error('no'), { name: 'NotAllowedError' }))
    const { result } = renderHook(() => useCamera())
    await act(async () => { await result.current.start() })
    await waitFor(() => expect(result.current.status).toBe('denied'))
  })

  it('reports nocamera when getUserMedia is unavailable', async () => {
    Object.defineProperty(navigator, 'mediaDevices', { configurable: true, value: undefined })
    const { result } = renderHook(() => useCamera())
    await act(async () => { await result.current.start() })
    expect(result.current.status).toBe('nocamera')
  })
})