1'use strict'; 2 3import { Asset } from 'expo-asset'; 4import Constants from 'expo-constants'; 5import * as FS from 'expo-file-system'; 6import { Platform } from 'expo-modules-core'; 7 8export const name = 'FileSystem'; 9 10export async function test({ describe, expect, it, ...t }) { 11 describe('FileSystem', () => { 12 const throws = async (run) => { 13 let error = null; 14 try { 15 await run(); 16 } catch (e) { 17 error = e; 18 } 19 expect(error).toBeTruthy(); 20 }; 21 22 if (Constants.appOwnership === 'expo') { 23 describe('managed workflow', () => { 24 it('throws out-of-scope exceptions', async () => { 25 const p = FS.documentDirectory; 26 27 await throws(() => FS.readAsStringAsync(p + '../hello/world')); 28 await throws(() => FS.writeAsStringAsync(p + '../hello/world', '')); 29 await throws(() => FS.deleteAsync(p + '../hello/world')); 30 await throws(() => FS.deleteAsync(p)); 31 await throws(() => FS.deleteAsync(FS.cacheDirectory)); 32 await throws(() => FS.moveAsync({ from: p + '../a/b', to: 'c' })); 33 await throws(() => FS.moveAsync({ from: 'c', to: p + '../a/b' })); 34 await throws(() => FS.copyAsync({ from: p + '../a/b', to: 'c' })); 35 await throws(() => FS.copyAsync({ from: 'c', to: p + '../a/b' })); 36 await throws(() => FS.makeDirectoryAsync(p + '../hello/world')); 37 await throws(() => FS.readDirectoryAsync(p + '../hello/world')); 38 await throws(() => FS.downloadAsync('http://www.google.com', p + '../hello/world')); 39 await throws(() => FS.readDirectoryAsync(p + '../')); 40 await throws(() => FS.downloadAsync('http://www.google.com', p + '../hello/world')); 41 }); 42 }); 43 } 44 45 if (Platform.OS === 'web') { 46 // Web doesn't support FileSystem 47 return; 48 } 49 50 it('delete(idempotent) -> !exists -> download(md5, uri) -> exists -> delete -> !exists', async () => { 51 const localUri = FS.documentDirectory + 'download1.png'; 52 53 const assertExists = async (expectedToExist) => { 54 const { exists } = await FS.getInfoAsync(localUri); 55 if (expectedToExist) { 56 expect(exists).toBeTruthy(); 57 } else { 58 expect(exists).not.toBeTruthy(); 59 } 60 }; 61 62 await FS.deleteAsync(localUri, { idempotent: true }); 63 await assertExists(false); 64 65 const { md5, headers } = await FS.downloadAsync( 66 'https://s3-us-west-1.amazonaws.com/test-suite-data/avatar2.png', 67 localUri, 68 { md5: true } 69 ); 70 expect(md5).toBe('1e02045c10b8f1145edc7c8375998f87'); 71 await assertExists(true); 72 expect(headers['Content-Type']).toBe('image/png'); 73 74 await FS.deleteAsync(localUri); 75 await assertExists(false); 76 }, 9000); 77 78 it('Can read/write Base64', async () => { 79 const asset = await Asset.fromModule(require('../assets/icons/app.png')); 80 await asset.downloadAsync(); 81 82 for (let startingPosition = 0; startingPosition < 3; startingPosition++) { 83 const options = { 84 encoding: FS.EncodingType.Base64, 85 position: startingPosition, 86 length: startingPosition + 1, 87 }; 88 89 const b64 = await FS.readAsStringAsync(asset.localUri, options); 90 expect(b64).toBeDefined(); 91 expect(typeof b64).toBe('string'); 92 expect(b64.length % 4).toBe(0); 93 94 const localUri = FS.documentDirectory + 'b64.png'; 95 96 await FS.writeAsStringAsync(localUri, b64, { encoding: FS.EncodingType.Base64 }); 97 98 expect(await FS.readAsStringAsync(localUri, { encoding: FS.EncodingType.Base64 })).toBe( 99 b64 100 ); 101 } 102 }); 103 104 it('delete(idempotent) -> delete[error]', async () => { 105 const localUri = FS.documentDirectory + 'willDelete.png'; 106 107 await FS.deleteAsync(localUri, { idempotent: true }); 108 109 let error; 110 try { 111 await FS.deleteAsync(localUri); 112 } catch (e) { 113 error = e; 114 } 115 expect(error.message).toMatch(/not exist/); 116 }); 117 118 it('download(md5, uri) -> read -> delete -> !exists -> read[error]', async () => { 119 const localUri = FS.documentDirectory + 'download1.txt'; 120 121 const { md5 } = await FS.downloadAsync( 122 'https://s3-us-west-1.amazonaws.com/test-suite-data/text-file.txt', 123 localUri, 124 { md5: true } 125 ); 126 expect(md5).toBe('86d73d2f11e507365f7ea8e7ec3cc4cb'); 127 128 const string = await FS.readAsStringAsync(localUri); 129 expect(string).toBe('hello, world\nthis is a test file\n'); 130 131 await FS.deleteAsync(localUri, { idempotent: true }); 132 133 let error; 134 try { 135 await FS.readAsStringAsync(localUri); 136 } catch (e) { 137 error = e; 138 } 139 expect(error).toBeTruthy(); 140 }, 9000); 141 142 it('delete(idempotent) -> !exists -> write -> read -> write -> read', async () => { 143 const localUri = FS.documentDirectory + 'write1.txt'; 144 145 await FS.deleteAsync(localUri, { idempotent: true }); 146 147 const { exists } = await FS.getInfoAsync(localUri); 148 expect(exists).not.toBeTruthy(); 149 150 const writeAndVerify = async (expected) => { 151 await FS.writeAsStringAsync(localUri, expected); 152 const string = await FS.readAsStringAsync(localUri); 153 expect(string).toBe(expected); 154 }; 155 156 await writeAndVerify('hello, world'); 157 await writeAndVerify('hello, world!!!!!!'); 158 }); 159 160 it('delete(new) -> 2 * [write -> move -> !exists(orig) -> read(new)]', async () => { 161 const from = FS.documentDirectory + 'from.txt'; 162 const to = FS.documentDirectory + 'to.txt'; 163 const contents = ['contents 1', 'contents 2']; 164 165 await FS.deleteAsync(to, { idempotent: true }); 166 167 // Move twice to make sure we can overwrite 168 for (let i = 0; i < 2; ++i) { 169 await FS.writeAsStringAsync(from, contents[i]); 170 171 await FS.moveAsync({ from, to }); 172 173 const { exists } = await FS.getInfoAsync(from); 174 expect(exists).not.toBeTruthy(); 175 176 expect(await FS.readAsStringAsync(to)).toBe(contents[i]); 177 } 178 }); 179 180 it('delete(new) -> 2 * [write -> copy -> exists(orig) -> read(new)]', async () => { 181 const from = FS.documentDirectory + 'from.txt'; 182 const to = FS.documentDirectory + 'to.txt'; 183 const contents = ['contents 1', 'contents 2']; 184 185 await FS.deleteAsync(to, { idempotent: true }); 186 187 // Copy twice to make sure we can overwrite 188 for (let i = 0; i < 2; ++i) { 189 await FS.writeAsStringAsync(from, contents[i]); 190 191 await FS.copyAsync({ from, to }); 192 193 const { exists } = await FS.getInfoAsync(from); 194 expect(exists).toBeTruthy(); 195 196 expect(await FS.readAsStringAsync(to)).toBe(contents[i]); 197 } 198 }); 199 200 it( 201 'delete(dir) -> write(dir/file)[error] -> mkdir(dir) ->' + 202 'mkdir(dir)[error] -> write(dir/file) -> read', 203 async () => { 204 let error; 205 const path = FS.documentDirectory + 'dir/file'; 206 const dir = FS.documentDirectory + 'dir'; 207 const contents = 'hello, world'; 208 209 await FS.deleteAsync(dir, { idempotent: true }); 210 211 error = null; 212 try { 213 await FS.writeAsStringAsync(path, contents); 214 } catch (e) { 215 error = e; 216 } 217 expect(error).toBeTruthy(); 218 219 await FS.makeDirectoryAsync(dir); 220 221 error = null; 222 try { 223 await FS.makeDirectoryAsync(dir); 224 } catch (e) { 225 error = e; 226 } 227 expect(error).toBeTruthy(); 228 229 await FS.writeAsStringAsync(path, contents); 230 231 expect(await FS.readAsStringAsync(path)).toBe(contents); 232 } 233 ); 234 235 it( 236 'delete(dir) -> write(dir/dir2/file)[error] -> ' + 237 'mkdir(dir/dir2, intermediates) -> ' + 238 'mkdir(dir/dir2, intermediates) -> write(dir/dir2/file) -> read', 239 async () => { 240 let error; 241 const path = FS.documentDirectory + 'dir/dir2/file'; 242 const dir = FS.documentDirectory + 'dir/dir2'; 243 const contents = 'hello, world'; 244 245 await FS.deleteAsync(dir, { idempotent: true }); 246 247 error = null; 248 try { 249 await FS.writeAsStringAsync(path, contents); 250 } catch (e) { 251 error = e; 252 } 253 expect(error).toBeTruthy(); 254 255 await FS.makeDirectoryAsync(dir, { 256 intermediates: true, 257 }); 258 259 error = null; 260 try { 261 await FS.makeDirectoryAsync(dir); 262 } catch (e) { 263 error = e; 264 } 265 expect(error).toBeTruthy(); 266 267 error = null; 268 try { 269 await FS.makeDirectoryAsync(dir, { 270 intermediates: true, 271 }); 272 } catch (e) { 273 error = e; 274 } 275 expect(error).toBe(null); 276 277 await FS.writeAsStringAsync(path, contents); 278 279 expect(await FS.readAsStringAsync(path)).toBe(contents); 280 } 281 ); 282 283 it('getInfo(dirPath)', async () => { 284 const dir = FS.documentDirectory + 'dir'; 285 const path = FS.documentDirectory + 'dir/file.txt'; 286 287 await FS.deleteAsync(dir, { idempotent: true }); 288 await FS.makeDirectoryAsync(dir, { 289 intermediates: true, 290 }); 291 await FS.writeAsStringAsync(path, 'Expo is awesome '); 292 const info = await FS.getInfoAsync(dir); 293 294 expect(info).toBeDefined(); 295 expect(info.exists).toBe(true); 296 expect(info.isDirectory).toBe(true); 297 expect(info.size).toBe(28); 298 }); 299 300 /* 301 This test fails in CI because of an exception being thrown by deleteAsync in the nativeModule. 302 I traced it down to the FileUtils.forceDelete call here: 303 https://github.com/expo/expo/blob/bcd136b096df84e0b0f72a15acbda45491de8201/packages/expo-file-system/android/src/main/java/expo/modules/filesystem/FileSystemModule.java#L294 304 it( 305 'delete(dir, idempotent) -> make tree -> check contents ' + 306 '-> check directory listings' + 307 '-> move -> check directory listings' + 308 '-> copy -> check directory listings', 309 async () => { 310 let error; 311 const dir = FS.documentDirectory + 'dir'; 312 313 await FS.deleteAsync(dir, { idempotent: true }); 314 315 await FS.makeDirectoryAsync(dir + '/child1', { 316 intermediates: true, 317 }); 318 await FS.makeDirectoryAsync(dir + '/child2', { 319 intermediates: true, 320 }); 321 322 await FS.writeAsStringAsync(dir + '/file1', 'contents1'); 323 await FS.writeAsStringAsync(dir + '/file2', 'contents2'); 324 325 await FS.writeAsStringAsync(dir + '/child1/file3', 'contents3'); 326 327 await FS.writeAsStringAsync(dir + '/child2/file4', 'contents4'); 328 await FS.writeAsStringAsync(dir + '/child2/file5', 'contents5'); 329 330 const checkContents = async (path, contents) => 331 expect(await FS.readAsStringAsync(path)).toBe(contents); 332 333 await checkContents(dir + '/file1', 'contents1'); 334 await checkContents(dir + '/file2', 'contents2'); 335 await checkContents(dir + '/child1/file3', 'contents3'); 336 await checkContents(dir + '/child2/file4', 'contents4'); 337 await checkContents(dir + '/child2/file5', 'contents5'); 338 339 const checkDirectory = async (path, expected) => { 340 const list = await FS.readDirectoryAsync(path); 341 expect(list.sort()).toEqual(expected.sort()); 342 }; 343 344 const checkRoot = async root => { 345 await checkDirectory(root, ['file1', 'file2', 'child1', 'child2']); 346 await checkDirectory(root + '/child1', ['file3']); 347 await checkDirectory(root + '/child2', ['file4', 'file5']); 348 349 error = null; 350 try { 351 await checkDirectory(root + '/file1', ['nope']); 352 } catch (e) { 353 error = e; 354 } 355 expect(error).toBeTruthy(); 356 }; 357 358 await checkRoot(dir); 359 360 await FS.deleteAsync(FS.documentDirectory + 'moved', { 361 idempotent: true, 362 }); 363 await FS.moveAsync({ from: dir, to: FS.documentDirectory + 'moved' }); 364 await checkRoot(FS.documentDirectory + 'moved'); 365 await FS.copyAsync({ 366 from: FS.documentDirectory + 'moved', 367 to: FS.documentDirectory + 'copied', 368 }); 369 await checkRoot(FS.documentDirectory + 'copied'); 370 } 371 ); 372 */ 373 374 it('delete(idempotent) -> download(md5) -> getInfo(size)', async () => { 375 const localUri = FS.documentDirectory + 'download1.png'; 376 377 await FS.deleteAsync(localUri, { idempotent: true }); 378 379 const { md5 } = await FS.downloadAsync( 380 'https://s3-us-west-1.amazonaws.com/test-suite-data/avatar2.png', 381 localUri, 382 { md5: true } 383 ); 384 expect(md5).toBe('1e02045c10b8f1145edc7c8375998f87'); 385 386 const { size, modificationTime } = await FS.getInfoAsync(localUri); 387 expect(size).toBe(3230); 388 const nowTime = 0.001 * new Date().getTime(); 389 expect(nowTime - modificationTime).toBeLessThan(3600); 390 391 await FS.deleteAsync(localUri); 392 }, 30000); 393 394 it('missing parameters', async () => { 395 const p = FS.documentDirectory + 'test'; 396 397 await throws(() => FS.moveAsync({ from: p })); 398 await throws(() => FS.moveAsync({ to: p })); 399 await throws(() => FS.copyAsync({ from: p })); 400 await throws(() => FS.copyAsync({ to: p })); 401 }); 402 403 it('can read root directories', async () => { 404 await FS.readDirectoryAsync(FS.documentDirectory); 405 await FS.readDirectoryAsync(FS.cacheDirectory); 406 }); 407 408 it('download(network failure)', async () => { 409 const localUri = FS.documentDirectory + 'download1.png'; 410 411 const assertExists = async (expectedToExist) => { 412 const { exists } = await FS.getInfoAsync(localUri); 413 if (expectedToExist) { 414 expect(exists).toBeTruthy(); 415 } else { 416 expect(exists).not.toBeTruthy(); 417 } 418 }; 419 420 await FS.deleteAsync(localUri, { idempotent: true }); 421 await assertExists(false); 422 423 let error; 424 try { 425 await FS.downloadAsync('https://nonexistent-subdomain.expo.io', localUri, { 426 md5: true, 427 sessionType: FS.FileSystemSessionType.FOREGROUND, 428 }); 429 } catch (e) { 430 error = e; 431 } 432 expect(error).toBeTruthy(); 433 await assertExists(false); 434 await FS.deleteAsync(localUri, { idempotent: true }); 435 }, 30000); 436 437 it('download(404)', async () => { 438 const localUri = FS.documentDirectory + 'download1.png'; 439 440 const assertExists = async (expectedToExist) => { 441 const { exists } = await FS.getInfoAsync(localUri); 442 if (expectedToExist) { 443 expect(exists).toBeTruthy(); 444 } else { 445 expect(exists).not.toBeTruthy(); 446 } 447 }; 448 449 await FS.deleteAsync(localUri, { idempotent: true }); 450 await assertExists(false); 451 452 const { status } = await FS.downloadAsync('https://github.com/omg1231sdfaljs', localUri, { 453 md5: true, 454 }); 455 await assertExists(true); 456 expect(status).toBe(404); 457 458 await FS.deleteAsync(localUri); 459 await assertExists(false); 460 }, 30000); 461 462 it('download(nonexistent local path)', async () => { 463 try { 464 const remoteUrl = 'https://s3-us-west-1.amazonaws.com/test-suite-data/avatar2.png'; 465 const localUri = FS.documentDirectory + 'doesnt/exists/download1.png'; 466 await FS.downloadAsync(remoteUrl, localUri); 467 } catch (err) { 468 expect(err.message).toMatch(/Directory '.*' does not exist/); 469 } 470 }, 30000); 471 472 it('mkdir(multi-level) + download(multi-level local path)', async () => { 473 const remoteUrl = 'https://s3-us-west-1.amazonaws.com/test-suite-data/avatar2.png'; 474 const localDirUri = FS.documentDirectory + 'foo/bar/baz'; 475 const localFileUri = localDirUri + 'download1.png'; 476 477 await FS.makeDirectoryAsync(localDirUri, { intermediates: true }); 478 479 await FS.downloadAsync(remoteUrl, localFileUri); 480 }, 30000); 481 482 it('create UTF-8 folder and get info', async () => { 483 const folderName = '中文'; 484 const folderUri = FS.documentDirectory + folderName; 485 486 const dirInfo = await FS.getInfoAsync(folderUri); 487 if (dirInfo.exists) { 488 await FS.deleteAsync(folderUri); 489 } 490 491 await FS.makeDirectoryAsync(folderUri); 492 const newDirInfo = await FS.getInfoAsync(folderUri); 493 494 expect(newDirInfo.exists).toBeTruthy(); 495 expect(newDirInfo.isDirectory).toBeTruthy(); 496 }, 30000); 497 }); 498} 499