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