@fakes/media-devices
Version:
A interactive fake implementation of MediaDevices interface in the browser for testing
169 lines (151 loc) • 6.51 kB
text/typescript
import {
allAccessGranted,
allAccessDenied,
anyCamera,
anyDevice,
anyMicrophone,
forgeMediaDevices,
MediaDevicesControl,
RequestedMediaInput,
stillHaveToAskForDeviceAccess,
} from './index'
import './matchers/dom-exception'
import './matchers/to-include-video-track'
let control: MediaDevicesControl
afterEach(() => {
control.uninstall()
})
describe('MediaDevicesFake', () => {
describe('still have to ask for device access', () => {
beforeEach(() => {
control = forgeMediaDevices(stillHaveToAskForDeviceAccess())
control.installInto(window)
})
test('return a video stream from an attached camera after granting access', async () => {
control.attach(anyCamera({ label: 'The Camera' }))
const navigator = window.navigator
expect((await navigator.permissions.query({ name: 'camera' })).state).toBe('prompt')
const userMediaPromise = navigator.mediaDevices.getUserMedia({ video: true })
const permissionPrompt = await control.deviceAccessPrompt()
expect(permissionPrompt.requestedPermissions()).toEqual([RequestedMediaInput.Camera])
permissionPrompt.takeAction('allow')
expect((await userMediaPromise).getVideoTracks()[0].label).toEqual('The Camera')
expect((await navigator.permissions.query({ name: 'camera' })).state).toBe('granted')
// 2nd time should not need a permission prompt
expect(await navigator.mediaDevices.getUserMedia({ video: true })).toIncludeVideoTrack()
})
test('reject promise with a DomException after blocking access', async () => {
const { attach, deviceAccessPrompt } = control
attach(anyMicrophone())
const navigator = window.navigator
const userMediaPromise = navigator.mediaDevices.getUserMedia({ audio: true })
const permissionPrompt = await deviceAccessPrompt()
expect(permissionPrompt.requestedPermissions()).toEqual([RequestedMediaInput.Microphone])
permissionPrompt.takeAction('block')
await expect(userMediaPromise).rejects.domException('Permission denied', 'NotAllowedError')
expect((await navigator.permissions.query({ name: 'microphone' })).state).toBe('denied')
// 2nd time should not need a permission prompt
await expect(navigator.mediaDevices.getUserMedia({ audio: true })).rejects.domException(
'Permission denied',
'NotAllowedError',
)
})
})
describe('device access granted', () => {
beforeEach(() => {
control = forgeMediaDevices(allAccessGranted())
control.installInto(window)
})
test('return a video stream from an attached camera', async () => {
control.attach(anyCamera({ label: 'The Camera' }))
const userMedia = await window.navigator.mediaDevices.getUserMedia({ video: true })
expect(userMedia.getVideoTracks()[0].label).toEqual('The Camera')
})
test('reject media stream requests if no device is available', async () => {
const onlyMicrophone = anyMicrophone()
control.attach(onlyMicrophone)
control.remove(onlyMicrophone)
await expect(window.navigator.mediaDevices.getUserMedia({ audio: true })).rejects.domException(
'Requested device not found',
'NotFoundError',
)
})
})
describe('device access blocked', () => {
beforeEach(() => {
control = forgeMediaDevices(allAccessDenied())
control.installInto(window)
})
test('reject media stream requests', async () => {
await expect(window.navigator.mediaDevices.getUserMedia({ audio: true })).rejects.domException(
'Permission denied',
'NotAllowedError',
)
})
})
describe('attach', () => {
beforeEach(() => {
control = forgeMediaDevices(allAccessDenied())
control.installInto(window)
})
test('an array of devices', async () => {
control.attach([anyDevice()])
const devices = await window.navigator.mediaDevices.enumerateDevices()
expect(devices).toHaveLength(1)
})
})
})
describe('PermissionsFake', () => {
beforeEach(() => {
control = forgeMediaDevices(stillHaveToAskForDeviceAccess())
control.installInto(window)
})
describe('notify onchange listener on permission state change', () => {
test('for camera', async () => {
control.attach(anyCamera())
const navigator = window.navigator
const permissionStatus = await navigator.permissions.query({ name: 'camera' })
expect(permissionStatus.state).toEqual('prompt')
const onChange = jest.fn()
const onchange = jest.fn()
permissionStatus.onchange = onChange
permissionStatus.addEventListener('change', onchange)
navigator.mediaDevices.getUserMedia({ video: true })
const prompt = await control.deviceAccessPrompt()
prompt.takeAction('allow')
expect(onChange).toHaveBeenCalled()
expect(permissionStatus.state).toEqual('granted')
})
test('for microphone', async () => {
const navigator = window.navigator
control.attach(anyMicrophone())
const permissionStatus = await navigator.permissions.query({ name: 'microphone' })
expect(permissionStatus.state).toEqual('prompt')
const onChange = jest.fn()
const onchange = jest.fn()
permissionStatus.onchange = onChange
permissionStatus.addEventListener('change', onchange)
navigator.mediaDevices.getUserMedia({ audio: true }).catch(() => {})
const prompt = await control.deviceAccessPrompt()
prompt.takeAction('block')
expect(onChange).toHaveBeenCalled()
expect(onchange).toHaveBeenCalled()
expect(permissionStatus.state).toEqual('denied')
})
test('change permission after creation in one call', async () => {
control.setPermissionFor({ camera: 'granted', microphone: 'granted' })
expect(await permissionStateFor('camera')).toBe('granted')
expect(await permissionStateFor('microphone')).toBe('granted')
control.setPermissionFor({ camera: 'denied' })
expect(await permissionStateFor('camera')).toBe('denied')
expect(await permissionStateFor('microphone')).toBe('granted')
control.setPermissionFor({ microphone: 'denied' })
expect(await permissionStateFor('camera')).toBe('denied')
expect(await permissionStateFor('microphone')).toBe('denied')
})
})
})
const permissionStateFor = async (name: 'camera' | 'microphone') => {
const cameraPermission = await window.navigator.permissions.query({ name })
return cameraPermission.state
}