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