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