1'use strict'; 2 3import { ScreenOrientation } from 'expo'; 4import { Platform } from 'react-native'; 5 6export const name = 'ScreenOrientation'; 7 8const convertToCoarseOrientation = orientation => { 9 if ( 10 orientation === ScreenOrientation.Orientation.PORTRAIT_UP || 11 orientation === ScreenOrientation.Orientation.PORTRAIT_DOWN 12 ) { 13 return ScreenOrientation.Orientation.PORTRAIT; 14 } else if ( 15 orientation === ScreenOrientation.Orientation.LANDSCAPE_LEFT || 16 orientation === ScreenOrientation.Orientation.LANDSCAPE_RIGHT 17 ) { 18 return ScreenOrientation.Orientation.LANDSCAPE; 19 } else { 20 return orientation; 21 } 22}; 23 24// Wait until we are in desiredOrientation 25// Fail if we are not in a validOrientation 26const applyAsync = ({ desiredOrientationLock, desiredOrientations, validOrientations }) => { 27 if (Platform.OS === 'ios' && desiredOrientations) { 28 // ios can only detect orientation of coarse granularity (ie) 'PORTRAIT'/'LANDSCAPE' 29 desiredOrientations = desiredOrientations.map(orientation => 30 convertToCoarseOrientation(orientation) 31 ); 32 } 33 if (Platform.OS === 'ios' && validOrientations) { 34 // ios can only detect orientation of coarse granularity (ie) 'PORTRAIT'/'LANDSCAPE' 35 validOrientations = validOrientations.map(orientation => 36 convertToCoarseOrientation(orientation) 37 ); 38 } 39 return new Promise(async function(resolve, reject) { 40 let subscriptionCancelled = false; 41 const subscription = ScreenOrientation.addOrientationChangeListener(update => { 42 const { orientationInfo, orientationLock } = update; 43 const { orientation } = orientationInfo; 44 if (validOrientations && !validOrientations.includes(orientation)) { 45 reject(new Error(`Should not have received an orientation of ${orientation}`)); 46 } 47 48 if (desiredOrientations && !desiredOrientations.includes(orientation)) { 49 return; 50 } else if (desiredOrientationLock && orientationLock !== desiredOrientationLock) { 51 return; 52 } 53 54 // We have met all the desired orientation conditions 55 // remove itself 56 if (!subscriptionCancelled) { 57 ScreenOrientation.removeOrientationChangeListener(subscription); 58 subscriptionCancelled = true; 59 } 60 61 // resolve promise 62 resolve(); 63 }); 64 65 if (desiredOrientationLock) { 66 // set the screen orientation to desired orientation lock 67 await ScreenOrientation.lockAsync(desiredOrientationLock); 68 } 69 70 const orientationInfo = await ScreenOrientation.getOrientationAsync(); 71 const { orientation } = orientationInfo; 72 const orientationLock = await ScreenOrientation.getOrientationLockAsync(); 73 74 if (desiredOrientations && !desiredOrientations.includes(orientation)) { 75 return; 76 } else if (desiredOrientationLock && orientationLock !== desiredOrientationLock) { 77 return; 78 } 79 80 // We have met all the desired orientation conditions 81 // remove previous subscription 82 if (!subscriptionCancelled) { 83 ScreenOrientation.removeOrientationChangeListener(subscription); 84 subscriptionCancelled = true; 85 } 86 resolve(); 87 }); 88}; 89 90export function test(t) { 91 t.describe('Screen Orientation', () => { 92 t.describe('Screen Orientation locking, getters, setters, listeners, etc', () => { 93 t.beforeEach(async () => { 94 // Put the screen back to PORTRAIT_UP 95 const desiredOrientation = ScreenOrientation.Orientation.PORTRAIT_UP; 96 97 await applyAsync({ 98 desiredOrientationLock: ScreenOrientation.OrientationLock.PORTRAIT_UP, 99 desiredOrientations: [desiredOrientation], 100 }); 101 }); 102 t.afterEach(async () => { 103 ScreenOrientation.removeOrientationChangeListeners(); 104 }); 105 t.it( 106 'Sets screen to landscape orientation and gets the correct orientationLock', 107 async () => { 108 // set the screen orientation to LANDSCAPE LEFT lock 109 await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE_LEFT); 110 111 // detect the correct orientationLock policy immediately 112 const orientationLock = await ScreenOrientation.getOrientationLockAsync(); 113 t.expect(orientationLock).toBe(ScreenOrientation.OrientationLock.LANDSCAPE_LEFT); 114 } 115 ); 116 117 t.it('Sets screen to landscape orientation and gets the correct orientation', async () => { 118 const desiredOrientationLock = ScreenOrientation.OrientationLock.LANDSCAPE_LEFT; 119 const desiredOrientation = ScreenOrientation.Orientation.LANDSCAPE_LEFT; 120 const validOrientations = [ 121 ScreenOrientation.Orientation.LANDSCAPE_LEFT, 122 ScreenOrientation.Orientation.PORTRAIT_UP, 123 ]; 124 await applyAsync({ 125 desiredOrientationLock, 126 desiredOrientations: [desiredOrientation], 127 validOrientations, 128 }); 129 130 const orientationInfo = await ScreenOrientation.getOrientationAsync(); 131 const { orientation } = orientationInfo; 132 // ios can only detect orientation with coarse granularity 133 t.expect([ 134 ScreenOrientation.Orientation.LANDSCAPE_LEFT, 135 ScreenOrientation.Orientation.LANDSCAPE, 136 ]).toContain(orientation); 137 }); 138 139 // We rely on RN to emit `didUpdateDimensions` 140 // If this method no longer works, it's possible that the underlying RN implementation has changed 141 // see https://github.com/facebook/react-native/blob/c31f79fe478b882540d7fd31ee37b53ddbd60a17/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/DeviceInfoModule.java#L90 142 t.it( 143 'Register for the callback, set to landscape orientation and get the correct orientation', 144 async () => { 145 const callListenerAsync = new Promise(async function(resolve, reject) { 146 // Register for screen orientation changes 147 ScreenOrientation.addOrientationChangeListener(update => { 148 const { orientationInfo } = update; 149 const { orientation } = orientationInfo; 150 if ( 151 orientation === ScreenOrientation.Orientation.PORTRAIT_UP || 152 orientation === ScreenOrientation.Orientation.PORTRAIT // ios can only detect orientation with coarse granularity 153 ) { 154 // orientation update has not happened yet 155 } else if ( 156 orientation === ScreenOrientation.Orientation.LANDSCAPE_LEFT || 157 orientation === ScreenOrientation.Orientation.LANDSCAPE // ios can only detect orientation with coarse granularity 158 ) { 159 resolve(); 160 } else { 161 reject(new Error(`Should not be in orientation: ${orientation}`)); 162 } 163 }); 164 165 // Put the screen to LANDSCAPE_LEFT 166 await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE_LEFT); 167 }); 168 169 // Wait for listener to get called 170 await callListenerAsync; 171 } 172 ); 173 174 t.it('Unlock the screen orientation back to default', async () => { 175 // Put the screen to LANDSCAPE_LEFT 176 await applyAsync({ 177 desiredOrientationLock: ScreenOrientation.OrientationLock.LANDSCAPE_LEFT, 178 desiredOrientations: [ScreenOrientation.Orientation.LANDSCAPE_LEFT], 179 }); 180 181 // Unlock the screen orientation 182 await ScreenOrientation.unlockAsync(); 183 184 // detect the correct orientationLock policy immediately 185 const orientationLock = await ScreenOrientation.getOrientationLockAsync(); 186 t.expect(orientationLock).toBe(ScreenOrientation.OrientationLock.DEFAULT); 187 }); 188 189 // This test only applies to android devices 190 if (Platform.OS === 'android') { 191 t.it('Apply a native android lock', async () => { 192 // Apply the native USER_LANDSCAPE android lock (11) 193 // https://developer.android.com/reference/android/R.attr#screenOrientation 194 await ScreenOrientation.lockPlatformAsync({ screenOrientationConstantAndroid: 11 }); 195 196 // detect the correct orientationLock policy immediately 197 const orientationLock = await ScreenOrientation.getOrientationLockAsync(); 198 t.expect(orientationLock).toBe(ScreenOrientation.OrientationLock.OTHER); 199 200 // expect the native platform getter to return correctly 201 const platformInfo = await ScreenOrientation.getPlatformOrientationLockAsync(); 202 const { screenOrientationConstantAndroid } = platformInfo; 203 t.expect(screenOrientationConstantAndroid).toBe(11); 204 205 const desiredOrientations = [ 206 ScreenOrientation.Orientation.LANDSCAPE_RIGHT, 207 ScreenOrientation.Orientation.LANDSCAPE_LEFT, 208 ]; 209 const validOrientations = [ 210 ScreenOrientation.Orientation.LANDSCAPE_RIGHT, 211 ScreenOrientation.OrientationLock.LANDSCAPE_LEFT, 212 ScreenOrientation.Orientation.PORTRAIT_UP, 213 ]; 214 await applyAsync({ desiredOrientations, validOrientations }); 215 }); 216 } 217 218 // This test only applies to ios devices 219 if (Platform.OS === 'ios') { 220 t.it('Apply a native iOS lock', async () => { 221 // Allow only PORTRAIT_UP and LANDSCAPE_RIGHT 222 await ScreenOrientation.lockPlatformAsync({ 223 screenOrientationArrayIOS: [ 224 ScreenOrientation.Orientation.PORTRAIT_UP, 225 ScreenOrientation.Orientation.LANDSCAPE_RIGHT, 226 ], 227 }); 228 229 // detect the correct orientationLock policy immediately 230 const orientationLock = await ScreenOrientation.getOrientationLockAsync(); 231 t.expect(orientationLock).toBe(ScreenOrientation.OrientationLock.OTHER); 232 233 // expect the native platform getter to return correctly 234 const platformInfo = await ScreenOrientation.getPlatformOrientationLockAsync(); 235 const { screenOrientationArrayIOS } = platformInfo; 236 t.expect(screenOrientationArrayIOS).toContain( 237 ScreenOrientation.Orientation.LANDSCAPE_RIGHT 238 ); 239 t.expect(screenOrientationArrayIOS).toContain(ScreenOrientation.Orientation.PORTRAIT_UP); 240 241 const desiredOrientations = [ 242 ScreenOrientation.Orientation.PORTRAIT_UP, 243 ScreenOrientation.Orientation.LANDSCAPE_RIGHT, 244 ]; 245 const validOrientations = [ 246 ScreenOrientation.Orientation.PORTRAIT_UP, 247 ScreenOrientation.Orientation.LANDSCAPE_RIGHT, 248 ]; 249 await applyAsync({ desiredOrientations, validOrientations }); 250 }); 251 } 252 253 t.it('Remove all listeners and expect them never to be called', async () => { 254 // Register for screen orientation changes 255 let listenerWasCalled = false; 256 ScreenOrientation.addOrientationChangeListener(() => { 257 listenerWasCalled = true; 258 }); 259 260 ScreenOrientation.addOrientationChangeListener(() => { 261 listenerWasCalled = true; 262 }); 263 264 ScreenOrientation.removeOrientationChangeListeners(); 265 266 // set the screen orientation to LANDSCAPE LEFT lock 267 await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE_LEFT); 268 269 // If we set a different lock and wait for it to be applied without ever having the 270 // listeners invoked, we assume they've been successfully removed 271 const desiredOrientations = [ScreenOrientation.Orientation.LANDSCAPE_LEFT]; 272 const validOrientations = [ 273 ScreenOrientation.Orientation.LANDSCAPE_LEFT, 274 ScreenOrientation.Orientation.PORTRAIT_UP, 275 ]; 276 await applyAsync({ desiredOrientations, validOrientations }); 277 278 // expect listeners to not have been called 279 t.expect(listenerWasCalled).toBe(false); 280 }); 281 282 /* 283 This test fails about half the time on CI with the error that it expected false to be true. 284 This means that the check that subscription2Called is true fails. 285 It may be a problem with the removeOrientationChangeListener implementation since 286 this is the only test that calls that function on an external subscription while another is active. 287 288 t.it('Register some listeners and remove a subset', async () => { 289 // Register for screen orientation changes 290 let subscription1Called = false; 291 let subscription2Called = false; 292 293 const subscription1 = ScreenOrientation.addOrientationChangeListener(() => { 294 subscription1Called = true; 295 }); 296 297 ScreenOrientation.addOrientationChangeListener(() => { 298 subscription2Called = true; 299 }); 300 301 // remove subscription1 ONLY 302 ScreenOrientation.removeOrientationChangeListener(subscription1); 303 304 // set the screen orientation to LANDSCAPE LEFT lock 305 await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.LANDSCAPE_LEFT); 306 307 // If we set a different lock and wait for it to be applied without ever having the 308 // listeners invoked, we assume they've been successfully removed 309 const desiredOrientations = [ScreenOrientation.Orientation.LANDSCAPE_LEFT]; 310 const validOrientations = [ 311 ScreenOrientation.Orientation.LANDSCAPE_LEFT, 312 ScreenOrientation.Orientation.PORTRAIT_UP, 313 ]; 314 await applyAsync({ desiredOrientations, validOrientations }); 315 316 // expect subscription1 to NOT have been called 317 t.expect(subscription1Called).toBe(false); 318 319 // expect subscription2 to have been called 320 t.expect(subscription2Called).toBe(true); 321 }); 322 */ 323 324 t.it('ensure that we correctly detect our supported orientationLocks', async () => { 325 // orientation locks that we should be able to apply 326 const acceptedLocks = [ 327 ScreenOrientation.OrientationLock.ALL, 328 ScreenOrientation.OrientationLock.LANDSCAPE_RIGHT, 329 ]; 330 331 for (const lock of acceptedLocks) { 332 const supported = await ScreenOrientation.supportsOrientationLockAsync(lock); 333 t.expect(supported).toBe(true); 334 } 335 336 // This is not a lock policy that we can apply 337 const unsupportedLock = ScreenOrientation.OrientationLock.OTHER; 338 const supported = await ScreenOrientation.supportsOrientationLockAsync(unsupportedLock); 339 t.expect(supported).toBe(false); 340 341 // Expect non-lock values to throw an error 342 const notLocks = ['FOO', 3]; 343 for (const notLock of notLocks) { 344 let hasError = false; 345 try { 346 await ScreenOrientation.supportsOrientationLockAsync(notLock); 347 } catch (e) { 348 hasError = true; 349 } 350 t.expect(hasError).toBe(true); 351 } 352 }); 353 }); 354 }); 355} 356