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