UNPKG

@fakes/media-devices

Version:

A interactive fake implementation of MediaDevices interface in the browser for testing

213 lines (200 loc) 6.43 kB
export type MediaStreamCheckResult = { success: boolean; messages?: Array<string> } type MediaStreamPredicate = (mediaStream: MediaStream) => MediaStreamCheckResult type ErrorPredicate = (error: Error) => MediaStreamCheckResult type MediaStreamPromisePredicate = (mediaStream: Promise<MediaStream>) => Promise<MediaStreamCheckResult> const mediaStream: (input: MediaStreamPredicate) => MediaStreamPromisePredicate = (input: MediaStreamPredicate) => { return async (promise: Promise<MediaStream>) => input(await promise) } const error: (input: ErrorPredicate) => MediaStreamPromisePredicate = (input: ErrorPredicate) => { return async (promise: Promise<MediaStream>) => { try { await promise return { success: false, messages: ['expected a rejected promise'] } } catch (e) { return input(e) } } } type MediaStreamCheck = { what: string predicate: MediaStreamPromisePredicate } interface Expected { description: string checks: MediaStreamCheck[] } type Matrix = Record<PermissionState, Expected | undefined> export interface Scenario { summary: string description: string constraints?: MediaStreamConstraints expected: Matrix } export const passUndefined: Scenario = { summary: 'undefined constraints', description: 'pass undefined as constraints', constraints: undefined, expected: { prompt: undefined, denied: undefined, granted: { description: 'reject and communicate that at least one constrain has to be present', checks: [ { what: 'TypeError', predicate: error((err) => { const success = err instanceof TypeError const messages = [`got: ${err.toString()}`] return { success, messages } }), }, { what: 'error message', predicate: error((err) => { const expected = `Failed to execute 'getUserMedia' on 'MediaDevices': At least one of audio and video must be requested` const success = err.message === expected const messages = [`expected: ${expected}`, `got: '${err.message}'`] return { success, messages } }), }, ], }, }, } export const requestedDeviceTypeNotAttached: Scenario = { summary: 'requested device type not attached', description: 'Requesting a camera but none is attached', constraints: { video: true }, expected: { prompt: undefined, denied: undefined, granted: { description: 'reject and communicate that the requested device was not found', checks: [ { what: 'DOMException', predicate: error((err) => { const success = err instanceof DOMException const messages = [`got: ${err.toString()}`] return { success, messages } }), }, { what: 'error message', predicate: error((err) => { const expected = `Requested device not found` const success = err.message === expected const messages = [`expected: ${expected}`, `got: '${err.message}'`] return { success, messages } }), }, ], }, }, } export const allConstraintsFalse: Scenario = { summary: 'all constraints false', description: 'pass false to the video and audio constraint', constraints: { audio: false, video: false, }, expected: { prompt: undefined, denied: undefined, granted: { description: 'reject and communicate that at least one constrain has to be set to true', checks: [ { what: 'TypeError', predicate: error((err) => { const success = err instanceof TypeError const messages = [`got: ${err.toString()}`] return { success, messages } }), }, { what: 'error message', predicate: error((err) => { const expected = `Failed to execute 'getUserMedia' on 'MediaDevices': At least one of audio and video must be requested` const success = err.message === expected const messages = [`expected: ${expected}`, `got: '${err.message}'`] return { success, messages } }), }, ], }, }, } export const noDeviceWithDeviceId: Scenario = { summary: 'bogus device id', description: 'the constraint contains a deviceId that no device has', constraints: { video: { deviceId: 'bogus' } }, expected: { prompt: undefined, denied: undefined, granted: { description: 'fallback to any other audio device', checks: [ { what: 'stream is active', predicate: mediaStream((stream) => { const success = stream.active return { success } }), }, { what: 'stream has an id', predicate: mediaStream((stream) => { const success = stream.id.length > 0 return { success } }), }, ], }, }, } export const existingDevice: Scenario = { summary: 'existing device', description: 'any camera device without any other constraints', constraints: { video: true }, expected: { prompt: undefined, denied: { description: 'should be rejected', checks: [ { what: 'DOMException', predicate: error((error) => { const success = error instanceof DOMException const messages = [`got: ${error.constructor.name}`] return { success, messages } }), }, { what: 'NotAllowedError', predicate: error((error) => { const actual = error.name const success = actual === 'NotAllowedError' const messages = [`got: ${actual}`] return { success, messages } }), }, ], }, granted: { description: 'tbd', checks: [], }, }, } const collectScenarios = () => { const result = new Map<string, Scenario>() result.set(existingDevice.summary, existingDevice) result.set(noDeviceWithDeviceId.summary, noDeviceWithDeviceId) result.set(passUndefined.summary, passUndefined) result.set(allConstraintsFalse.summary, allConstraintsFalse) result.set(requestedDeviceTypeNotAttached.summary, requestedDeviceTypeNotAttached) return result } export const scenarios = collectScenarios()