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 return class PersistenceExpo { 209 type = 'LOCAL'; 210 211 /** @return {Promise<boolean>} */ 212 async _isAvailable() { 213 try { 214 if (!storage) { 215 return false; 216 } 217 await storage.setItem(STORAGE_AVAILABLE_KEY, '1'); 218 await storage.removeItem(STORAGE_AVAILABLE_KEY); 219 return true; 220 } catch { 221 return false; 222 } 223 } 224 225 /** 226 * @param {string} key 227 * @param {Record<string, unknown>|string} value 228 * @return {Promise<void>} 229 */ 230 _set(key, value) { 231 return storage.setItem(key, JSON.stringify(value)); 232 } 233 234 /** 235 * @param {string} key 236 * @param {Record<string, unknown>|string} value 237 * @return {Promise<Record<string, unknown>|string|null>} 238 */ 239 async _get(key) { 240 const json = await storage.getItem(key); 241 return json ? JSON.parse(json) : null; 242 } 243 244 /** 245 * @param {string} key 246 * @return {Promise<void>} 247 */ 248 _remove(key) { 249 return storage.removeItem(key); 250 } 251 252 /** 253 * @param {string} key 254 * @param {(value: Record<string, unknown>|string|null) => void} _listener 255 * @return {void} 256 */ 257 _addListener(_key, _listener) { 258 // Listeners are not supported for React Native storage. 259 } 260 261 /** 262 * @param {string} key 263 * @param {(value: Record<string, unknown>|string|null) => void} _listener 264 * @return {void} 265 */ 266 _removeListener(_key, _listener) { 267 // Listeners are not supported for React Native storage. 268 } 269 }; 270} 271