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