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