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