1import AsyncStorage from '@react-native-async-storage/async-storage'; 2import * as FileSystem from 'expo-file-system'; 3import { initializeApp, getApp } from 'firebase/app'; 4import { 5 initializeAuth, 6 getAuth, 7 signInWithEmailAndPassword, 8 signInWithPhoneNumber, 9 PhoneAuthProvider, 10} from 'firebase/auth'; 11import { getDatabase, ref, onValue } from 'firebase/database'; 12import { getFirestore, query, collection, where, doc, getDocs, getDoc } from 'firebase/firestore'; 13import { getFunctions, httpsCallable } from 'firebase/functions'; 14import { getStorage, ref as storageRef, listAll, getDownloadURL } from 'firebase/storage'; 15 16// The modules below require browser features and are not compatible within the react-native context. 17// import {...} from "firebase/analytics"; 18// import {...} from "firebase/remote-config"; 19// import {...} from "firebase/messaging"; 20// import {...} from "firebase/performance"; 21// import {...} from "firebase/installations"; 22 23import { expectMethodToThrowAsync } from '../TestUtils'; 24 25export const name = 'FirebaseJSSDK'; 26 27const FIREBASE_CONFIG = { 28 apiKey: 'AIzaSyD2gZuX5utrLBAdJoMrAdrMW7Sv9xQ5uBE', 29 authDomain: 'expo-payments.firebaseapp.com', 30 databaseURL: 'https://expo-payments.firebaseio.com', 31 projectId: 'expo-payments', 32 storageBucket: 'expo-payments.appspot.com', 33 messagingSenderId: '482324271988', 34 appId: '1:482324271988:web:9597460d096749b3f8d221', 35 measurementId: 'G-498KQSTM5G', 36}; 37 38export async function test({ describe, it, expect, beforeAll }) { 39 // Firebase can't reinitialize parts of their SDK, let's try and silently ignore if it fails. 40 beforeAll(() => { 41 try { 42 initializeApp(FIREBASE_CONFIG); 43 } catch {} 44 45 try { 46 // We need to use `@react-native-async-storage/async-storage` instead of `react-native`. 47 // See: https://github.com/firebase/firebase-js-sdk/issues/1847 48 initializeAuth(getApp(), { persistence: getReactNativePersistence(AsyncStorage) }); 49 } catch {} 50 }); 51 52 describe('FirebaseJSSDK', async () => { 53 describe('auth', async () => { 54 it(`calls getAuth() succesfully`, () => { 55 expect(getAuth()).not.toBeNull(); 56 }); 57 58 it(`returns correct sign-in error`, async () => { 59 const error = await expectMethodToThrowAsync(() => 60 signInWithEmailAndPassword(getAuth(), '[email protected]', '0') 61 ); 62 expect(error.code).toBe('auth/operation-not-allowed'); 63 }); 64 }); 65 66 describe('database', async () => { 67 it(`calls getDatabase() succesfully`, () => { 68 expect(getDatabase()).not.toBeNull(); 69 }); 70 71 it(`reads data once`, async () => { 72 let error = null; 73 try { 74 const db = getDatabase(); 75 const reference = ref(db, '/test1'); 76 onValue(reference, (snapshot) => { 77 expect(snapshot.val()).toBe('foobar'); 78 }); 79 } catch (e) { 80 error = e; 81 } 82 expect(error).toBeNull(); 83 }); 84 }); 85 86 describe('firestore', async () => { 87 it(`calls getFirestore() succesfully`, () => { 88 expect(getFirestore()).not.toBeNull(); 89 }); 90 91 it(`gets a collection`, async () => { 92 let error = null; 93 try { 94 const q = query(collection(getFirestore(), 'tests'), where('foo', '==', 'bar')); 95 const querySnapshot = await getDocs(q); 96 expect(querySnapshot.size).toBe(1); 97 querySnapshot.forEach((doc) => { 98 expect(doc.data().foo).toBe('bar'); 99 }); 100 } catch (e) { 101 error = e; 102 } 103 expect(error).toBeNull(); 104 }); 105 106 it(`gets a document`, async () => { 107 let error = null; 108 try { 109 const q = query(doc(getFirestore(), 'tests/doc1')); 110 const querySnapshot = await getDoc(q); 111 expect(querySnapshot).not.toBeNull(); 112 const data = querySnapshot.data(); 113 expect(data.foo).toBe('bar'); 114 } catch (e) { 115 error = e; 116 } 117 expect(error).toBeNull(); 118 }); 119 }); 120 121 describe('functions', async () => { 122 it(`calls getFunctions() succesfully`, () => { 123 expect(getFunctions()).not.toBeNull(); 124 }); 125 126 it(`calls the echo function`, async () => { 127 let error = null; 128 try { 129 const functions = getFunctions(); 130 const message = "I'm a unit test"; 131 const echoMessage = httpsCallable(functions, 'echoMessage'); 132 const response = await echoMessage({ message }); 133 const responseMessage = response.data.message; 134 expect(responseMessage).toBe(`Hi , you said: ${message}`); 135 } catch (e) { 136 error = e; 137 } 138 expect(error).toBeNull(); 139 }); 140 }); 141 142 describe('storage', () => { 143 it(`calls getStorage() succesfully`, () => { 144 expect(getStorage()).not.toBeNull(); 145 }); 146 147 it(`lists all files`, async () => { 148 let error = null; 149 try { 150 const storage = getStorage(); 151 const publicRef = storageRef(storage, 'public'); 152 const files = await listAll(publicRef); 153 expect(files.items.length).toBeGreaterThan(0); 154 } catch (e) { 155 error = e; 156 } 157 expect(error).toBeNull(); 158 }); 159 160 it(`downloads a file`, async () => { 161 let error = null; 162 try { 163 const storage = getStorage(); 164 const publicRef = storageRef(storage, 'public'); 165 const files = await listAll(publicRef); 166 expect(files.items.length).toBeGreaterThan(0); 167 const file = files.items[0]; 168 const downloadUrl = await getDownloadURL(file); 169 expect(typeof downloadUrl).toBe('string'); 170 const startUrl = 'https://firebasestorage.googleapis.com'; 171 expect(downloadUrl.substring(0, startUrl.length)).toBe(startUrl); 172 const { uri } = await FileSystem.downloadAsync( 173 downloadUrl, 174 FileSystem.documentDirectory + file.name 175 ); 176 expect(typeof uri).toBe('string'); 177 expect(uri).not.toBeNull(); 178 } catch (e) { 179 error = e; 180 } 181 expect(error).toBeNull(); 182 }); 183 }); 184 }); 185 186 describe('regression', () => { 187 // see: https://github.com/firebase/firebase-js-sdk/issues/5638 188 describe('firebase/auth', () => { 189 it('exports signInWithPhoneNumber', () => { 190 expect(signInWithPhoneNumber).not.toBe(undefined); 191 }); 192 193 it('exports PhoneAuthProvider', () => { 194 expect(PhoneAuthProvider).not.toBe(undefined); 195 }); 196 }); 197 }); 198} 199 200/** 201 * This is a replacement for the internal `getReactNativePersistence`. 202 * When this function is exposed, or if Firebase switches to the new AsyncStorage, we can remove this. 203 * 204 * @see https://github.com/firebase/firebase-js-sdk/blob/cdada6c68f9740d13dd6674bcb658e28e68253b6/packages/auth/src/platform_react_native/persistence/react_native.ts#L42-L85 205 * @see https://github.com/firebase/firebase-js-sdk/issues/1847#issuecomment-929482013 206 */ 207function getReactNativePersistence(storage) { 208 // https://github.com/firebase/firebase-js-sdk/blob/6dacc2400fdcf4432ed1977ca1eb148da6db3fc5/packages/auth/src/core/persistence/index.ts#L33 209 const STORAGE_AVAILABLE_KEY = '__sak'; 210 211 return class PersistenceExpo { 212 type = 'LOCAL'; 213 214 /** @return {Promise<boolean>} */ 215 async _isAvailable() { 216 try { 217 if (!storage) { 218 return false; 219 } 220 await storage.setItem(STORAGE_AVAILABLE_KEY, '1'); 221 await storage.removeItem(STORAGE_AVAILABLE_KEY); 222 return true; 223 } catch { 224 return false; 225 } 226 } 227 228 /** 229 * @param {string} key 230 * @param {Record<string, unknown>|string} value 231 * @return {Promise<void>} 232 */ 233 _set(key, value) { 234 return storage.setItem(key, JSON.stringify(value)); 235 } 236 237 /** 238 * @param {string} key 239 * @param {Record<string, unknown>|string} value 240 * @return {Promise<Record<string, unknown>|string|null>} 241 */ 242 async _get(key) { 243 const json = await storage.getItem(key); 244 return json ? JSON.parse(json) : null; 245 } 246 247 /** 248 * @param {string} key 249 * @return {Promise<void>} 250 */ 251 _remove(key) { 252 return storage.removeItem(key); 253 } 254 255 /** 256 * @param {string} key 257 * @param {(value: Record<string, unknown>|string|null) => void} _listener 258 * @return {void} 259 */ 260 _addListener(_key, _listener) { 261 // Listeners are not supported for React Native storage. 262 } 263 264 /** 265 * @param {string} key 266 * @param {(value: Record<string, unknown>|string|null) => void} _listener 267 * @return {void} 268 */ 269 _removeListener(_key, _listener) { 270 // Listeners are not supported for React Native storage. 271 } 272 }; 273} 274