1'use strict'; 2 3import React from 'react'; 4import { Platform } from 'react-native'; 5import { Video } from 'expo-av'; 6import * as Permissions from 'expo-permissions'; 7import { Camera } from 'expo-camera'; 8import * as TestUtils from '../TestUtils'; 9 10import { waitFor, mountAndWaitFor as originalMountAndWaitFor, retryForStatus } from './helpers'; 11 12export const name = 'Camera'; 13const style = { width: 200, height: 200 }; 14 15export async function test(t, { setPortalChild, cleanupPortal }) { 16 const shouldSkipTestsRequiringPermissions = await TestUtils.shouldSkipTestsRequiringPermissionsAsync(); 17 const describeWithPermissions = shouldSkipTestsRequiringPermissions ? t.xdescribe : t.describe; 18 19 describeWithPermissions('Camera', () => { 20 let instance = null; 21 let originalTimeout; 22 23 const refSetter = ref => { 24 instance = ref; 25 }; 26 27 const mountAndWaitFor = (child, propName = 'onCameraReady') => 28 new Promise(resolve => { 29 const response = originalMountAndWaitFor(child, propName, setPortalChild); 30 setTimeout(() => resolve(response), 1500); 31 }); 32 33 t.beforeAll(async () => { 34 await TestUtils.acceptPermissionsAndRunCommandAsync(() => { 35 return Permissions.askAsync(Permissions.CAMERA); 36 }); 37 await TestUtils.acceptPermissionsAndRunCommandAsync(() => { 38 return Permissions.askAsync(Permissions.AUDIO_RECORDING); 39 }); 40 41 originalTimeout = t.jasmine.DEFAULT_TIMEOUT_INTERVAL; 42 t.jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout * 3; 43 }); 44 45 t.afterAll(() => { 46 t.jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; 47 }); 48 49 t.beforeEach(async () => { 50 const { status } = await Permissions.getAsync(Permissions.CAMERA); 51 t.expect(status).toEqual('granted'); 52 }); 53 54 t.afterEach(async () => { 55 instance = null; 56 await cleanupPortal(); 57 }); 58 59 if (Platform.OS === 'android') { 60 t.describe('Camera.getSupportedRatiosAsync', () => { 61 t.it('returns an array of strings', async () => { 62 await mountAndWaitFor(<Camera style={style} ref={refSetter} />); 63 const ratios = await instance.getSupportedRatiosAsync(); 64 t.expect(ratios instanceof Array).toBe(true); 65 t.expect(ratios.length).toBeGreaterThan(0); 66 }); 67 }); 68 } 69 70 t.describe('Camera.takePictureAsync', () => { 71 t.it('returns a local URI', async () => { 72 await mountAndWaitFor(<Camera ref={refSetter} style={style} />); 73 const picture = await instance.takePictureAsync(); 74 t.expect(picture).toBeDefined(); 75 t.expect(picture.uri).toMatch(/^file:\/\//); 76 }); 77 78 t.it('returns `width` and `height` of the image', async () => { 79 await mountAndWaitFor(<Camera ref={refSetter} style={style} />); 80 let picture = await instance.takePictureAsync(); 81 t.expect(picture).toBeDefined(); 82 t.expect(picture.width).toBeDefined(); 83 t.expect(picture.height).toBeDefined(); 84 }); 85 86 t.it('returns EXIF only if requested', async () => { 87 await mountAndWaitFor(<Camera ref={refSetter} style={style} />); 88 let picture = await instance.takePictureAsync({ exif: false }); 89 t.expect(picture).toBeDefined(); 90 t.expect(picture.exif).not.toBeDefined(); 91 92 picture = await instance.takePictureAsync({ exif: true }); 93 t.expect(picture).toBeDefined(); 94 t.expect(picture.exif).toBeDefined(); 95 }); 96 97 t.it('returns Base64 only if requested', async () => { 98 await mountAndWaitFor(<Camera ref={refSetter} style={style} />); 99 let picture = await instance.takePictureAsync({ base64: false }); 100 t.expect(picture).toBeDefined(); 101 t.expect(picture.base64).not.toBeDefined(); 102 103 picture = await instance.takePictureAsync({ base64: true }); 104 t.expect(picture).toBeDefined(); 105 t.expect(picture.base64).toBeDefined(); 106 }); 107 108 t.it('returns proper `exif.Flash % 2 = 0` if the flash is off', async () => { 109 await mountAndWaitFor( 110 <Camera ref={refSetter} flashMode={Camera.Constants.FlashMode.off} style={style} /> 111 ); 112 let picture = await instance.takePictureAsync({ exif: true }); 113 t.expect(picture).toBeDefined(); 114 t.expect(picture.exif).toBeDefined(); 115 t.expect(picture.exif.Flash % 2 === 0).toBe(true); 116 }); 117 118 if (Platform.OS === 'ios') { 119 // https://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif/flash.html 120 // Android returns invalid values! (I've tested the code on an Android tablet 121 // that has no flash and it returns Flash = 0, meaning that the flash did not fire, 122 // but is present.) 123 124 t.it('returns proper `exif.Flash % 2 = 1` if the flash is on', async () => { 125 await mountAndWaitFor( 126 <Camera ref={refSetter} flashMode={Camera.Constants.FlashMode.on} style={style} /> 127 ); 128 let picture = await instance.takePictureAsync({ exif: true }); 129 t.expect(picture).toBeDefined(); 130 t.expect(picture.exif).toBeDefined(); 131 t.expect(picture.exif.Flash % 2 === 1).toBe(true); 132 }); 133 } 134 135 // https://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif/whitebalance.html 136 137 t.it('returns `exif.WhiteBalance = 1` if white balance is manually set', async () => { 138 await mountAndWaitFor( 139 <Camera 140 style={style} 141 ref={refSetter} 142 whiteBalance={Camera.Constants.WhiteBalance.incandescent} 143 /> 144 ); 145 let picture = await instance.takePictureAsync({ exif: true }); 146 t.expect(picture).toBeDefined(); 147 t.expect(picture.exif).toBeDefined(); 148 t.expect(picture.exif.WhiteBalance).toEqual(1); 149 }); 150 151 t.it('returns `exif.WhiteBalance = 0` if white balance is set to auto', async () => { 152 await mountAndWaitFor( 153 <Camera style={style} ref={refSetter} whiteBalance={Camera.Constants.WhiteBalance.auto} /> 154 ); 155 let picture = await instance.takePictureAsync({ exif: true }); 156 t.expect(picture).toBeDefined(); 157 t.expect(picture.exif).toBeDefined(); 158 t.expect(picture.exif.WhiteBalance).toEqual(0); 159 }); 160 161 if (Platform.OS === 'ios') { 162 t.it('returns `exif.LensModel ~= back` if camera type is set to back', async () => { 163 await mountAndWaitFor( 164 <Camera style={style} ref={refSetter} type={Camera.Constants.Type.back} /> 165 ); 166 let picture = await instance.takePictureAsync({ exif: true }); 167 t.expect(picture).toBeDefined(); 168 t.expect(picture.exif).toBeDefined(); 169 t.expect(picture.exif.LensModel).toMatch('back'); 170 }); 171 172 t.it('returns `exif.LensModel ~= front` if camera type is set to front', async () => { 173 await mountAndWaitFor( 174 <Camera style={style} ref={refSetter} type={Camera.Constants.Type.front} /> 175 ); 176 let picture = await instance.takePictureAsync({ exif: true }); 177 t.expect(picture).toBeDefined(); 178 t.expect(picture.exif).toBeDefined(); 179 t.expect(picture.exif.LensModel).toMatch('front'); 180 }); 181 182 t.it('returns `exif.DigitalZoom ~= false` if zoom is not set', async () => { 183 await mountAndWaitFor(<Camera style={style} ref={refSetter} />); 184 let picture = await instance.takePictureAsync({ exif: true }); 185 t.expect(picture).toBeDefined(); 186 t.expect(picture.exif).toBeDefined(); 187 t.expect(picture.exif.DigitalZoomRatio).toBeFalsy(); 188 }); 189 190 t.it('returns `exif.DigitalZoom ~= false` if zoom is set to 0', async () => { 191 await mountAndWaitFor(<Camera style={style} ref={refSetter} zoom={0} />); 192 let picture = await instance.takePictureAsync({ exif: true }); 193 t.expect(picture).toBeDefined(); 194 t.expect(picture.exif).toBeDefined(); 195 t.expect(picture.exif.DigitalZoomRatio).toBeFalsy(); 196 }); 197 198 let smallerRatio = null; 199 200 t.it('returns `exif.DigitalZoom > 0` if zoom is set', async () => { 201 await mountAndWaitFor(<Camera style={style} ref={refSetter} zoom={0.5} />); 202 let picture = await instance.takePictureAsync({ exif: true }); 203 t.expect(picture).toBeDefined(); 204 t.expect(picture.exif).toBeDefined(); 205 t.expect(picture.exif.DigitalZoomRatio).toBeGreaterThan(0); 206 smallerRatio = picture.exif.DigitalZoomRatio; 207 }); 208 209 t.it( 210 'returns `exif.DigitalZoom`s monotonically increasing with the zoom value', 211 async () => { 212 await mountAndWaitFor(<Camera style={style} ref={refSetter} zoom={1} />); 213 let picture = await instance.takePictureAsync({ exif: true }); 214 t.expect(picture).toBeDefined(); 215 t.expect(picture.exif).toBeDefined(); 216 t.expect(picture.exif.DigitalZoomRatio).toBeGreaterThan(smallerRatio); 217 } 218 ); 219 } 220 }); 221 222 t.describe('Camera.recordAsync', () => { 223 t.beforeEach(async () => { 224 if (Platform.OS === 'ios') { 225 await waitFor(500); 226 } 227 }); 228 229 t.it('returns a local URI', async () => { 230 await mountAndWaitFor(<Camera ref={refSetter} style={style} />); 231 const recordingPromise = instance.recordAsync(); 232 await waitFor(2500); 233 instance.stopRecording(); 234 const response = await recordingPromise; 235 t.expect(response).toBeDefined(); 236 t.expect(response.uri).toMatch(/^file:\/\//); 237 }); 238 239 let recordedFileUri = null; 240 241 t.it('stops the recording after maxDuration', async () => { 242 await mountAndWaitFor(<Camera ref={refSetter} style={style} />); 243 const response = await instance.recordAsync({ maxDuration: 2 }); 244 recordedFileUri = response.uri; 245 }); 246 247 t.it('the video has a duration near maxDuration', async () => { 248 await mountAndWaitFor( 249 <Video style={style} source={{ uri: recordedFileUri }} ref={refSetter} />, 250 'onLoad' 251 ); 252 await retryForStatus(instance, { isBuffering: false }); 253 const video = await instance.getStatusAsync(); 254 t.expect(video.durationMillis).toBeLessThan(2250); 255 t.expect(video.durationMillis).toBeGreaterThan(1750); 256 }); 257 258 // Test for the fix to: https://github.com/expo/expo/issues/1976 259 const testFrontCameraRecording = async camera => { 260 await mountAndWaitFor(camera); 261 const response = await instance.recordAsync({ maxDuration: 2 }); 262 263 await mountAndWaitFor( 264 <Video style={style} source={{ uri: response.uri }} ref={refSetter} />, 265 'onLoad' 266 ); 267 await retryForStatus(instance, { isBuffering: false }); 268 const video = await instance.getStatusAsync(); 269 270 t.expect(video.durationMillis).toBeLessThan(2250); 271 t.expect(video.durationMillis).toBeGreaterThan(1750); 272 }; 273 274 t.it('records using the front camera', async () => { 275 await testFrontCameraRecording( 276 <Camera 277 ref={refSetter} 278 style={style} 279 type={Camera.Constants.Type.front} 280 useCamera2Api={false} 281 /> 282 ); 283 }); 284 285 if (Platform.OS === 'android') { 286 t.it('records using the front camera and Camera2 API', async () => { 287 await testFrontCameraRecording( 288 <Camera 289 ref={refSetter} 290 style={style} 291 type={Camera.Constants.Type.front} 292 useCamera2Api 293 /> 294 ); 295 }); 296 } 297 298 t.it('stops the recording after maxFileSize', async () => { 299 await mountAndWaitFor(<Camera ref={refSetter} style={style} />); 300 await instance.recordAsync({ maxFileSize: 256 * 1024 }); // 256 KiB 301 }); 302 303 t.describe('can record consecutive clips', () => { 304 let defaultTimeoutInterval = null; 305 t.beforeAll(() => { 306 defaultTimeoutInterval = t.jasmine.DEFAULT_TIMEOUT_INTERVAL; 307 t.jasmine.DEFAULT_TIMEOUT_INTERVAL = defaultTimeoutInterval * 2; 308 }); 309 310 t.afterAll(() => { 311 t.jasmine.DEFAULT_TIMEOUT_INTERVAL = defaultTimeoutInterval; 312 }); 313 314 t.it('started/stopped manually', async () => { 315 await mountAndWaitFor(<Camera style={style} ref={refSetter} />); 316 317 const recordFor = duration => 318 new Promise(async (resolve, reject) => { 319 const recordingPromise = instance.recordAsync(); 320 await waitFor(duration); 321 instance.stopRecording(); 322 try { 323 const recordedVideo = await recordingPromise; 324 t.expect(recordedVideo).toBeDefined(); 325 t.expect(recordedVideo.uri).toBeDefined(); 326 resolve(); 327 } catch (error) { 328 reject(error); 329 } 330 }); 331 332 await recordFor(1000); 333 await waitFor(1000); 334 await recordFor(1000); 335 }); 336 337 t.it('started/stopped automatically', async () => { 338 await mountAndWaitFor(<Camera style={style} ref={refSetter} />); 339 340 const recordFor = duration => 341 new Promise(async (resolve, reject) => { 342 try { 343 const response = await instance.recordAsync({ maxDuration: duration / 1000 }); 344 resolve(response); 345 } catch (error) { 346 reject(error); 347 } 348 }); 349 350 await recordFor(1000); 351 await recordFor(1000); 352 }); 353 }); 354 }); 355 }); 356} 357