UNPKG

24.3 kBPlain TextView Raw
1/* tslint:disable:no-implicit-dependencies no-any */
2/**
3 * BarcodePickerCameraManager tests
4 */
5import test from "ava";
6import * as sinon from "sinon";
7
8import { Camera, CameraAccess } from "..";
9import { BarcodePickerCameraManager, ExtendedMediaTrackCapabilities, MeteringMode } from "./barcodePickerCameraManager";
10import { BarcodePickerGui } from "./barcodePickerGui";
11
12Object.defineProperty(screen, "width", {
13 writable: true
14});
15Object.defineProperty(screen, "height", {
16 writable: true
17});
18(<any>screen).width = 100;
19(<any>screen).height = 100;
20
21const triggerFatalErrorSpy: sinon.SinonSpy = sinon.spy();
22
23// Speed up times
24(<any>BarcodePickerCameraManager).cameraAccessTimeoutMs /= 10;
25(<any>BarcodePickerCameraManager).cameraMetadataCheckTimeoutMs /= 10;
26(<any>BarcodePickerCameraManager).cameraMetadataCheckIntervalMs /= 10;
27(<any>BarcodePickerCameraManager).getCapabilitiesTimeoutMs /= 10;
28(<any>BarcodePickerCameraManager).autofocusIntervalMs /= 10;
29(<any>BarcodePickerCameraManager).manualToAutofocusResumeTimeoutMs /= 10;
30(<any>BarcodePickerCameraManager).manualFocusWaitTimeoutMs /= 10;
31
32async function wait(ms: number): Promise<void> {
33 return new Promise(resolve => {
34 setTimeout(resolve, ms);
35 });
36}
37
38function fakeGetCameras(cameraAmount: number, cameraTypes?: Camera.Type[], cameraLabels?: string[]): void {
39 if ((<sinon.SinonSpy>CameraAccess.getCameras).restore != null) {
40 (<sinon.SinonSpy>CameraAccess.getCameras).restore();
41 }
42 sinon.stub(CameraAccess, "getCameras").resolves(
43 // tslint:disable-next-line:prefer-array-literal
44 Array.from(Array(cameraAmount), (_, index) => {
45 const cameraType: Camera.Type =
46 cameraTypes == null || cameraTypes[index] == null ? Camera.Type.BACK : cameraTypes[index];
47 const label: string =
48 cameraLabels == null || cameraLabels[index] == null
49 ? `Fake Camera Device (${cameraType})`
50 : cameraLabels[index];
51
52 return {
53 deviceId: "unknown",
54 groupId: "1",
55 kind: "videoinput",
56 label,
57 cameraType
58 };
59 })
60 );
61}
62
63function fakeAccessCameraStream(facingMode: string, mediaTrackCapabilities?: ExtendedMediaTrackCapabilities): void {
64 if ((<sinon.SinonSpy>CameraAccess.accessCameraStream).restore != null) {
65 (<sinon.SinonSpy>CameraAccess.accessCameraStream).restore();
66 }
67 sinon.stub(CameraAccess, "accessCameraStream").callsFake(() => {
68 const mediaStreamTrack: MediaStreamTrack = <any>{
69 stop: sinon.spy(),
70 addEventListener: sinon.spy(),
71 getSettings: () => {
72 return {
73 width: 640,
74 height: 480,
75 deviceId: "1",
76 facingMode
77 };
78 },
79 label: ""
80 };
81
82 if (mediaTrackCapabilities != null) {
83 mediaStreamTrack.getCapabilities = () => {
84 return mediaTrackCapabilities;
85 };
86 }
87
88 return Promise.resolve(<any>{
89 getTracks: () => {
90 return [mediaStreamTrack];
91 },
92 getVideoTracks: () => {
93 return [mediaStreamTrack];
94 }
95 });
96 });
97}
98
99function fakeAccessCameraStreamFailure(error: Error): void {
100 if ((<sinon.SinonSpy>CameraAccess.accessCameraStream).restore != null) {
101 (<sinon.SinonSpy>CameraAccess.accessCameraStream).restore();
102 }
103 sinon.stub(CameraAccess, "accessCameraStream").rejects(error);
104}
105
106function fakeMediaStream(
107 cameraManager: BarcodePickerCameraManager,
108 mediaTrackCapabilities?: ExtendedMediaTrackCapabilities
109): MediaStream {
110 const mediaStreamTrack: MediaStreamTrack = <any>{
111 constraints: {},
112 stop: sinon.spy(),
113 // tslint:disable-next-line:no-accessor-field-mismatch
114 getConstraints(): MediaTrackConstraints {
115 return this.constraints;
116 },
117 applyConstraints: sinon.stub().callsFake(
118 (mediaTrackConstraints: MediaTrackConstraints): Promise<void> => {
119 (<any>mediaStreamTrack).constraints = mediaTrackConstraints;
120
121 return Promise.resolve();
122 }
123 )
124 };
125
126 if (mediaTrackCapabilities != null) {
127 mediaStreamTrack.getCapabilities = () => {
128 return mediaTrackCapabilities;
129 };
130 }
131 const mediaStream: MediaStream = <any>{
132 getVideoTracks: () => {
133 return [mediaStreamTrack];
134 }
135 };
136 (<any>cameraManager).mediaStream = mediaStream;
137 (<any>cameraManager).storeStreamCapabilities();
138
139 return mediaStream;
140}
141
142test("isCameraSwitcherEnabled & setCameraSwitcherEnabled", async t => {
143 const barcodePickerGui: sinon.SinonStubbedInstance<BarcodePickerGui> = sinon.createStubInstance(BarcodePickerGui);
144 const cameraManager: BarcodePickerCameraManager = new BarcodePickerCameraManager(triggerFatalErrorSpy, <
145 BarcodePickerGui
146 >(<unknown>barcodePickerGui));
147 cameraManager.setInteractionOptions(false, false, false, false);
148 t.false(cameraManager.isCameraSwitcherEnabled());
149 t.is(barcodePickerGui.setCameraSwitcherVisible.callCount, 0);
150 fakeGetCameras(1);
151 await cameraManager.setCameraSwitcherEnabled(true);
152 t.is(barcodePickerGui.setCameraSwitcherVisible.callCount, 0);
153 fakeGetCameras(2);
154 await cameraManager.setCameraSwitcherEnabled(true);
155 t.is(barcodePickerGui.setCameraSwitcherVisible.callCount, 1);
156 t.deepEqual(barcodePickerGui.setCameraSwitcherVisible.lastCall.args, [true]);
157 t.true(cameraManager.isCameraSwitcherEnabled());
158 await cameraManager.setCameraSwitcherEnabled(false);
159 t.is(barcodePickerGui.setCameraSwitcherVisible.callCount, 2);
160 t.deepEqual(barcodePickerGui.setCameraSwitcherVisible.lastCall.args, [false]);
161 t.false(cameraManager.isCameraSwitcherEnabled());
162});
163
164test("isTorchToggleEnabled & setTorchToggleEnabled", t => {
165 const barcodePickerGui: sinon.SinonStubbedInstance<BarcodePickerGui> = sinon.createStubInstance(BarcodePickerGui);
166 const cameraManager: BarcodePickerCameraManager = new BarcodePickerCameraManager(triggerFatalErrorSpy, <
167 BarcodePickerGui
168 >(<unknown>barcodePickerGui));
169 cameraManager.setInteractionOptions(false, false, false, false);
170 t.false(cameraManager.isTorchToggleEnabled());
171 t.is(barcodePickerGui.setTorchTogglerVisible.callCount, 0);
172 cameraManager.setTorchToggleEnabled(true);
173 t.is(barcodePickerGui.setTorchTogglerVisible.callCount, 0);
174 fakeMediaStream(cameraManager, {
175 torch: true
176 });
177 cameraManager.setTorchToggleEnabled(true);
178 t.is(barcodePickerGui.setTorchTogglerVisible.callCount, 1);
179 t.deepEqual(barcodePickerGui.setTorchTogglerVisible.lastCall.args, [true]);
180 t.true(cameraManager.isTorchToggleEnabled());
181 cameraManager.setTorchToggleEnabled(false);
182 t.is(barcodePickerGui.setTorchTogglerVisible.callCount, 2);
183 t.deepEqual(barcodePickerGui.setTorchTogglerVisible.lastCall.args, [false]);
184 t.false(cameraManager.isTorchToggleEnabled());
185});
186
187test("isTapToFocusEnabled & setTapToFocusEnabled & isPinchToZoomEnabled & setPinchToZoomEnabled", t => {
188 const barcodePickerGui: sinon.SinonStubbedInstance<BarcodePickerGui> = sinon.createStubInstance(BarcodePickerGui);
189 const videoElementAddEventListener: sinon.SinonSpy = sinon.spy();
190 const videoElementRemoveEventListener: sinon.SinonSpy = sinon.spy();
191 (<any>barcodePickerGui).videoElement = {
192 addEventListener: videoElementAddEventListener,
193 removeEventListener: videoElementRemoveEventListener
194 };
195 const cameraManager: BarcodePickerCameraManager = new BarcodePickerCameraManager(triggerFatalErrorSpy, <
196 BarcodePickerGui
197 >(<unknown>barcodePickerGui));
198 cameraManager.setInteractionOptions(false, false, false, false);
199
200 t.false(cameraManager.isTapToFocusEnabled());
201 t.false(cameraManager.isPinchToZoomEnabled());
202 t.is(videoElementAddEventListener.callCount, 0);
203 cameraManager.setTapToFocusEnabled(true);
204 cameraManager.setPinchToZoomEnabled(true);
205 t.is(videoElementAddEventListener.callCount, 0);
206 fakeMediaStream(cameraManager);
207 cameraManager.setTapToFocusEnabled(true);
208 t.is(videoElementAddEventListener.callCount, 2);
209 t.true(videoElementAddEventListener.calledWith("mousedown"));
210 t.true(videoElementAddEventListener.calledWith("touchend"));
211 cameraManager.setPinchToZoomEnabled(true);
212 t.is(videoElementAddEventListener.callCount, 4);
213 t.true(videoElementAddEventListener.calledWith("touchstart"));
214 t.true(videoElementAddEventListener.calledWith("touchmove"));
215 t.true(cameraManager.isTapToFocusEnabled());
216 t.true(cameraManager.isPinchToZoomEnabled());
217 t.is(videoElementRemoveEventListener.callCount, 0);
218 cameraManager.setTapToFocusEnabled(false);
219 t.is(videoElementRemoveEventListener.callCount, 2);
220 t.true(videoElementRemoveEventListener.calledWith("mousedown"));
221 t.true(videoElementRemoveEventListener.calledWith("touchend"));
222 cameraManager.setPinchToZoomEnabled(false);
223 t.is(videoElementRemoveEventListener.callCount, 4);
224 t.true(videoElementRemoveEventListener.calledWith("touchstart"));
225 t.true(videoElementRemoveEventListener.calledWith("touchmove"));
226 t.false(cameraManager.isTapToFocusEnabled());
227 t.false(cameraManager.isPinchToZoomEnabled());
228});
229
230test("setTorchEnabled & toggleTorch", async t => {
231 const barcodePickerGui: sinon.SinonStubbedInstance<BarcodePickerGui> = sinon.createStubInstance(BarcodePickerGui);
232 const cameraManager: BarcodePickerCameraManager = new BarcodePickerCameraManager(triggerFatalErrorSpy, <
233 BarcodePickerGui
234 >(<unknown>barcodePickerGui));
235
236 await cameraManager.setTorchEnabled(true);
237
238 const mediaTrackCapabilities: ExtendedMediaTrackCapabilities = {
239 torch: true
240 };
241 const applyConstraintsStub: sinon.SinonStub = <any>(
242 fakeMediaStream(cameraManager, mediaTrackCapabilities).getVideoTracks()[0].applyConstraints
243 );
244 t.true(applyConstraintsStub.notCalled);
245 await cameraManager.setTorchEnabled(true);
246 t.true(applyConstraintsStub.calledOnce);
247 t.true(applyConstraintsStub.calledWith({ advanced: [{ torch: true }] }));
248 await cameraManager.setTorchEnabled(false);
249 t.true(applyConstraintsStub.calledTwice);
250 t.true(applyConstraintsStub.calledWith({ advanced: [{ torch: false }] }));
251 applyConstraintsStub.resetHistory();
252 await cameraManager.toggleTorch();
253 t.true(applyConstraintsStub.calledOnce);
254 t.true(applyConstraintsStub.calledWith({ advanced: [{ torch: true }] }));
255 await cameraManager.toggleTorch();
256 t.true(applyConstraintsStub.calledTwice);
257 t.true(applyConstraintsStub.calledWith({ advanced: [{ torch: false }] }));
258});
259
260test("setZoom", async t => {
261 const barcodePickerGui: sinon.SinonStubbedInstance<BarcodePickerGui> = sinon.createStubInstance(BarcodePickerGui);
262 const cameraManager: BarcodePickerCameraManager = new BarcodePickerCameraManager(triggerFatalErrorSpy, <
263 BarcodePickerGui
264 >(<unknown>barcodePickerGui));
265
266 await cameraManager.setZoom(2);
267
268 const mediaTrackCapabilities: ExtendedMediaTrackCapabilities = {
269 zoom: {
270 max: 9,
271 min: 1,
272 step: 0.1
273 }
274 };
275 const applyConstraintsStub: sinon.SinonStub = <any>(
276 fakeMediaStream(cameraManager, mediaTrackCapabilities).getVideoTracks()[0].applyConstraints
277 );
278 t.true(applyConstraintsStub.notCalled);
279 await cameraManager.setZoom(0);
280 t.deepEqual(applyConstraintsStub.lastCall.args, [{ advanced: [{ zoom: 1 }] }]);
281 await cameraManager.setZoom(1);
282 t.deepEqual(applyConstraintsStub.lastCall.args, [{ advanced: [{ zoom: 9 }] }]);
283 await cameraManager.setZoom(0.5);
284 t.deepEqual(applyConstraintsStub.lastCall.args, [{ advanced: [{ zoom: 5 }] }]);
285 await cameraManager.setZoom(10);
286 t.deepEqual(applyConstraintsStub.lastCall.args, [{ advanced: [{ zoom: 9 }] }]);
287 await cameraManager.setZoom(0.25, 5);
288 t.deepEqual(applyConstraintsStub.lastCall.args, [{ advanced: [{ zoom: 7 }] }]);
289});
290
291test("triggerZoomStart & triggerZoomMove", async t => {
292 const barcodePickerGui: sinon.SinonStubbedInstance<BarcodePickerGui> = sinon.createStubInstance(BarcodePickerGui);
293 const cameraManager: BarcodePickerCameraManager = new BarcodePickerCameraManager(triggerFatalErrorSpy, <
294 BarcodePickerGui
295 >(<unknown>barcodePickerGui));
296 const touchStartEvent: any = {
297 preventDefault: sinon.spy(),
298 type: "touchstart"
299 };
300 const touchStart0xEvent: any = {
301 ...touchStartEvent,
302 touches: [
303 {
304 screenX: 0,
305 screenY: 0
306 },
307 {
308 screenX: 0,
309 screenY: 0
310 }
311 ]
312 };
313 const touchStart25xEvent: any = {
314 ...touchStartEvent,
315 touches: [
316 {
317 screenX: 0,
318 screenY: 0
319 },
320 {
321 screenX: 25,
322 screenY: 0
323 }
324 ]
325 };
326 const touchStart50xEvent: any = {
327 ...touchStartEvent,
328 touches: [
329 {
330 screenX: 0,
331 screenY: 0
332 },
333 {
334 screenX: 50,
335 screenY: 0
336 }
337 ]
338 };
339
340 (<any>cameraManager).triggerZoomStart({
341 ...touchStartEvent,
342 touches: [1]
343 });
344 (<any>cameraManager).triggerZoomMove({
345 ...touchStartEvent,
346 touches: [1]
347 });
348 (<any>cameraManager).triggerZoomStart(touchStart25xEvent);
349
350 const mediaTrackCapabilities: ExtendedMediaTrackCapabilities = {
351 torch: true,
352 zoom: {
353 max: 9,
354 min: 1,
355 step: 0.1
356 }
357 };
358 const applyConstraintsStub: sinon.SinonStub = <any>(
359 fakeMediaStream(cameraManager, mediaTrackCapabilities).getVideoTracks()[0].applyConstraints
360 );
361 t.true(applyConstraintsStub.notCalled);
362 (<any>cameraManager).triggerZoomStart(touchStart0xEvent);
363 await cameraManager.setTorchEnabled(true);
364 (<any>cameraManager).triggerZoomStart(touchStart0xEvent);
365 (<any>cameraManager).triggerZoomMove(touchStart0xEvent);
366 t.deepEqual(applyConstraintsStub.lastCall.args, [{ advanced: [{ zoom: 1 }] }]);
367 (<any>cameraManager).triggerZoomMove(touchStart25xEvent);
368 t.deepEqual(applyConstraintsStub.lastCall.args, [{ advanced: [{ zoom: 5 }] }]);
369 (<any>cameraManager).triggerZoomMove(touchStart50xEvent);
370 t.deepEqual(applyConstraintsStub.lastCall.args, [{ advanced: [{ zoom: 9 }] }]);
371 (<any>cameraManager).triggerZoomStart(touchStart25xEvent);
372 (<any>cameraManager).triggerZoomMove(touchStart0xEvent);
373 t.deepEqual(applyConstraintsStub.lastCall.args, [{ advanced: [{ zoom: 5 }] }]);
374});
375
376// tslint:disable-next-line:max-func-body-length
377test("manual / auto focus", async t => {
378 const barcodePickerGui: sinon.SinonStubbedInstance<BarcodePickerGui> = sinon.createStubInstance(BarcodePickerGui);
379 const cameraManager: BarcodePickerCameraManager = new BarcodePickerCameraManager(triggerFatalErrorSpy, <
380 BarcodePickerGui
381 >(<unknown>barcodePickerGui));
382
383 (<any>cameraManager).triggerManualFocus({
384 preventDefault: sinon.spy(),
385 type: "touchend",
386 touches: [1, 2]
387 });
388
389 (<any>cameraManager).pinchToZoomDistance = 1;
390 (<any>cameraManager).triggerManualFocus({
391 preventDefault: sinon.spy(),
392 type: "mousedown"
393 });
394
395 (<any>cameraManager).triggerManualFocus({
396 preventDefault: sinon.spy(),
397 type: "touchend",
398 touches: []
399 });
400
401 // Trigger manual focus when not supported
402 let mediaTrackCapabilities: ExtendedMediaTrackCapabilities = {};
403 let applyConstraintsStub: sinon.SinonStub = <any>(
404 fakeMediaStream(cameraManager, mediaTrackCapabilities).getVideoTracks()[0].applyConstraints
405 );
406 t.true(applyConstraintsStub.notCalled);
407 (<any>cameraManager).triggerManualFocus();
408 t.true(applyConstraintsStub.notCalled);
409 mediaTrackCapabilities = {
410 focusMode: [MeteringMode.SINGLE_SHOT, MeteringMode.CONTINUOUS] // this is a weird mix
411 };
412 applyConstraintsStub = <any>(
413 fakeMediaStream(cameraManager, mediaTrackCapabilities).getVideoTracks()[0].applyConstraints
414 );
415 t.true(applyConstraintsStub.notCalled);
416 (<any>cameraManager).triggerManualFocus();
417 t.true(applyConstraintsStub.notCalled);
418
419 // Trigger manual focus when single-shot only is supported
420 mediaTrackCapabilities = {
421 focusMode: [MeteringMode.SINGLE_SHOT]
422 };
423 applyConstraintsStub = <any>(
424 fakeMediaStream(cameraManager, mediaTrackCapabilities).getVideoTracks()[0].applyConstraints
425 );
426 t.true(applyConstraintsStub.notCalled);
427 (<any>cameraManager).triggerManualFocus();
428 t.true(applyConstraintsStub.calledOnce);
429 t.true(applyConstraintsStub.calledWith({ advanced: [{ focusMode: MeteringMode.SINGLE_SHOT }] }));
430
431 // Enable background single-shot autofocus
432 applyConstraintsStub.resetHistory();
433 (<any>cameraManager).storeStreamCapabilities();
434 (<any>cameraManager).setupAutofocus();
435 await wait((<any>BarcodePickerCameraManager).autofocusIntervalMs * 4);
436 t.true(applyConstraintsStub.callCount >= 2);
437 t.true(applyConstraintsStub.alwaysCalledWith({ advanced: [{ focusMode: MeteringMode.SINGLE_SHOT }] }));
438 // Trigger manual focus when single-shot only is supported (while background single-shot autofocus is active)
439 (<any>cameraManager).triggerManualFocus();
440 applyConstraintsStub.resetHistory();
441 // Background single-shot autofocus should be disabled for a while
442 await wait((<any>BarcodePickerCameraManager).autofocusIntervalMs * 2);
443 t.true(applyConstraintsStub.notCalled);
444 await wait((<any>BarcodePickerCameraManager).manualToAutofocusResumeTimeoutMs * 2);
445 // Background single-shot autofocus should be enabled now
446 t.true(applyConstraintsStub.called);
447 t.true(applyConstraintsStub.alwaysCalledWith({ advanced: [{ focusMode: MeteringMode.SINGLE_SHOT }] }));
448 cameraManager.stopStream();
449
450 // Trigger manual focus when all focus modes are supported
451 mediaTrackCapabilities = {
452 focusMode: [MeteringMode.SINGLE_SHOT, MeteringMode.CONTINUOUS, MeteringMode.MANUAL]
453 };
454 applyConstraintsStub = <any>(
455 fakeMediaStream(cameraManager, mediaTrackCapabilities).getVideoTracks()[0].applyConstraints
456 );
457 (<any>cameraManager).triggerManualFocus();
458 t.true(applyConstraintsStub.calledOnce);
459 t.true(applyConstraintsStub.calledWith({ advanced: [{ focusMode: MeteringMode.CONTINUOUS }] }));
460 applyConstraintsStub.resetHistory();
461 await wait((<any>BarcodePickerCameraManager).manualFocusWaitTimeoutMs * 2);
462 t.true(applyConstraintsStub.calledOnce);
463 t.true(applyConstraintsStub.calledWith({ advanced: [{ focusMode: MeteringMode.MANUAL }] }));
464 applyConstraintsStub.resetHistory();
465 await wait((<any>BarcodePickerCameraManager).manualToAutofocusResumeTimeoutMs * 2);
466 t.true(applyConstraintsStub.calledOnce);
467 t.true(applyConstraintsStub.calledWith({ advanced: [{ focusMode: MeteringMode.CONTINUOUS }] }));
468});
469
470// tslint:disable-next-line:max-func-body-length
471test.serial("setupCameras", async t => {
472 const barcodePickerGui: sinon.SinonStubbedInstance<BarcodePickerGui> = sinon.createStubInstance(BarcodePickerGui);
473 const videoElementRemoveEventListener: sinon.SinonSpy = sinon.spy();
474 (<any>barcodePickerGui).videoElement = {
475 loadedmetadataEventListener: null,
476 addEventListener(eventType: string, listener: (this: HTMLVideoElement, ev: Event) => any): void {
477 if (eventType === "loadedmetadata") {
478 this.loadedmetadataEventListener = listener;
479 }
480 },
481 removeEventListener: videoElementRemoveEventListener,
482 dispatchEvent: sinon.spy()
483 };
484 (<any>barcodePickerGui).videoElement.load = function(): void {
485 this.loadedmetadataEventListener();
486 this.videoWidth = 640;
487 this.videoHeight = 480;
488 this.currentTime = 0;
489 this.onloadeddata();
490 setTimeout(() => {
491 this.currentTime = 1;
492 }, (<any>BarcodePickerCameraManager).cameraMetadataCheckIntervalMs * 2);
493 };
494 const cameraManager: BarcodePickerCameraManager = new BarcodePickerCameraManager(triggerFatalErrorSpy, <
495 BarcodePickerGui
496 >(<unknown>barcodePickerGui));
497 cameraManager.setInteractionOptions(true, true, true, true);
498 t.true(cameraManager.isCameraSwitcherEnabled());
499 t.is(barcodePickerGui.setCameraSwitcherVisible.callCount, 0);
500
501 // Intentionally make optimistic initial back camera access fail
502 fakeAccessCameraStream("user");
503 fakeGetCameras(2, [Camera.Type.FRONT, Camera.Type.FRONT]);
504 await cameraManager.setupCameras();
505 t.is(barcodePickerGui.setCameraSwitcherVisible.callCount, 1);
506 barcodePickerGui.setCameraSwitcherVisible.resetHistory();
507 t.is((<sinon.SinonSpy>CameraAccess.accessCameraStream).callCount, 2);
508 t.is((<sinon.SinonSpy>CameraAccess.getCameras).callCount, 1);
509 cameraManager.selectedCamera = undefined;
510 fakeAccessCameraStream("user");
511 fakeGetCameras(2, [Camera.Type.BACK, Camera.Type.FRONT]);
512 await cameraManager.setupCameras();
513 t.is(barcodePickerGui.setCameraSwitcherVisible.callCount, 1);
514 barcodePickerGui.setCameraSwitcherVisible.resetHistory();
515 t.is((<sinon.SinonSpy>CameraAccess.accessCameraStream).callCount, 2);
516 t.is((<sinon.SinonSpy>CameraAccess.getCameras).callCount, 1);
517 cameraManager.selectedCamera = undefined;
518 fakeAccessCameraStream("user");
519 fakeGetCameras(0);
520 let error: Error = await t.throwsAsync(cameraManager.setupCameras());
521 t.is(barcodePickerGui.setCameraSwitcherVisible.callCount, 0);
522 t.is(error.message, "No camera available");
523
524 // Access primary back camera in common triple camera setups
525 cameraManager.selectedCamera = undefined;
526 fakeAccessCameraStream("user");
527 fakeGetCameras(
528 3,
529 [Camera.Type.FRONT, Camera.Type.BACK, Camera.Type.BACK],
530 ["", "camera2 2, facing back", "camera2 0, facing back"]
531 );
532 await cameraManager.setupCameras();
533 t.not(cameraManager.selectedCamera, null);
534 t.is((<Camera>(<unknown>cameraManager.selectedCamera)).label, "camera2 0, facing back");
535
536 let mediaTrackCapabilities: ExtendedMediaTrackCapabilities = {
537 torch: true
538 };
539 cameraManager.selectedCamera = undefined;
540 fakeAccessCameraStream("environment", mediaTrackCapabilities);
541 fakeGetCameras(1);
542 barcodePickerGui.setCameraSwitcherVisible.resetHistory();
543 await cameraManager.setupCameras();
544 t.is(barcodePickerGui.setCameraSwitcherVisible.callCount, 0);
545 t.is((<sinon.SinonSpy>CameraAccess.accessCameraStream).callCount, 2);
546 t.is((<sinon.SinonSpy>CameraAccess.getCameras).callCount, 1);
547 await wait((<any>BarcodePickerCameraManager).getCapabilitiesTimeoutMs * 2);
548 t.deepEqual((<any>cameraManager).mediaTrackCapabilities, mediaTrackCapabilities);
549
550 mediaTrackCapabilities = {
551 torch: false,
552 focusMode: [MeteringMode.SINGLE_SHOT]
553 };
554 cameraManager.selectedCamera = undefined;
555 fakeAccessCameraStream("environment", mediaTrackCapabilities);
556 fakeGetCameras(2);
557 await cameraManager.setupCameras();
558 t.is(barcodePickerGui.setCameraSwitcherVisible.callCount, 1);
559 t.is((<sinon.SinonSpy>CameraAccess.accessCameraStream).callCount, 2);
560 t.is((<sinon.SinonSpy>CameraAccess.getCameras).callCount, 1);
561 await wait((<any>BarcodePickerCameraManager).getCapabilitiesTimeoutMs * 2);
562 t.deepEqual((<any>cameraManager).mediaTrackCapabilities, mediaTrackCapabilities);
563
564 (<any>barcodePickerGui).videoElement.load = function(): void {
565 this.loadedmetadataEventListener();
566 this.videoWidth = 640;
567 this.videoHeight = 480;
568 this.currentTime = 0;
569 this.onloadeddata();
570 // Intentionally never have valid metadata
571 };
572 error = await t.throwsAsync(cameraManager.setupCameras());
573 t.is(error.message, "Could not initialize camera correctly");
574 (<any>barcodePickerGui).videoElement.load = function(): void {
575 this.loadedmetadataEventListener();
576 // Intentionally never call onloadeddata()
577 };
578 error = await t.throwsAsync(cameraManager.setupCameras());
579 t.is(error.message, "Could not initialize camera correctly");
580
581 fakeAccessCameraStreamFailure(new Error("Test error 1"));
582 fakeGetCameras(1);
583 cameraManager.selectedCamera = undefined;
584 error = await t.throwsAsync(cameraManager.setupCameras());
585 t.is(error.message, "Test error 1");
586 t.true(
587 (<sinon.SinonSpy>CameraAccess.accessCameraStream)
588 .getCall(0)
589 .calledBefore((<sinon.SinonSpy>CameraAccess.getCameras).firstCall)
590 );
591 t.true(
592 (<sinon.SinonSpy>CameraAccess.accessCameraStream)
593 .getCall(4)
594 .calledAfter((<sinon.SinonSpy>CameraAccess.getCameras).firstCall)
595 );
596 t.is((<sinon.SinonSpy>CameraAccess.accessCameraStream).callCount, 8); // 2 times 4 calls (resolution fallbacks)
597 t.is((<sinon.SinonSpy>CameraAccess.getCameras).callCount, 1);
598
599 fakeAccessCameraStreamFailure(new Error("Test error 2"));
600 fakeGetCameras(1);
601 error = await t.throwsAsync(cameraManager.setupCameras());
602 t.is(error.message, "Test error 2");
603 t.true(
604 (<sinon.SinonSpy>CameraAccess.accessCameraStream)
605 .getCall(0)
606 .calledAfter((<sinon.SinonSpy>CameraAccess.getCameras).firstCall)
607 );
608 t.is((<sinon.SinonSpy>CameraAccess.accessCameraStream).callCount, 4); // 1 time 4 calls (resolution fallbacks)
609 t.is((<sinon.SinonSpy>CameraAccess.getCameras).callCount, 1);
610});