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