xref: /expo/apps/test-suite/tests/MediaLibrary.js (revision 3a818de0)
1import * as MediaLibrary from 'expo-media-library';
2import * as Permissions from 'expo-permissions';
3import { Asset } from 'expo-asset';
4import { Platform } from 'react-native';
5
6export const name = 'MediaLibrary';
7
8const FILES = [
9  require('../assets/icons/app.png'),
10  require('../assets/icons/loading.png'),
11  require('../assets/black-128x256.png'),
12  require('../assets/big_buck_bunny.mp4'),
13];
14
15const WAIT_TIME = 1000;
16const IMG_NUMBER = 3;
17const VIDEO_NUMBER = 1;
18const F_SIZE = IMG_NUMBER + VIDEO_NUMBER;
19const MEDIA_TYPES = [MediaLibrary.MediaType.photo, MediaLibrary.MediaType.video];
20const DEFAULT_MEDIA_TYPES = [MediaLibrary.MediaType.photo];
21const DEFAULT_PAGE_SIZE = 20;
22const ASSET_KEYS = [
23  'id',
24  'filename',
25  'uri',
26  'mediaType',
27  'width',
28  'height',
29  'creationTime',
30  'modificationTime',
31  'duration',
32  Platform.OS === 'ios' ? 'mediaSubtypes' : 'albumId',
33];
34
35const INFO_KEYS = [
36  'localUri',
37  'location',
38  'exif',
39  ...(Platform !== 'ios' ? [] : ['orientation', 'isFavorite']),
40];
41
42const ALBUM_KEYS = [
43  'id',
44  'title',
45  'assetCount',
46  ...(Platform !== 'ios'
47    ? []
48    : ['type', 'startTime', 'endTime', 'approximateLocation', 'locationNames']),
49];
50
51const GET_ASSETS_KEYS = ['assets', 'endCursor', 'hasNextPage', 'totalCount'];
52const ALBUM_NAME = 'Expo Test-Suite Album #1';
53const SECOND_ALBUM_NAME = 'Expo Test-Suite Album #2';
54const WRONG_NAME = 'wertyuiopdfghjklvbnhjnftyujn';
55const WRONG_ID = '1234567890';
56
57async function getFiles() {
58  await Promise.all(FILES.map(req => Asset.loadAsync(req)));
59  return FILES.map(file => Asset.fromModule(file));
60}
61
62async function getAssets(files) {
63  return await Promise.all(files.map(({ localUri }) => MediaLibrary.createAssetAsync(localUri)));
64}
65
66async function createAlbum(assets, name) {
67  const album = await MediaLibrary.createAlbumAsync(name, assets[0], false);
68  await MediaLibrary.addAssetsToAlbumAsync(assets.slice(1), album, false);
69  return album;
70}
71
72function timeoutWrapper(fun, time) {
73  return new Promise(resolve => {
74    setTimeout(() => {
75      fun();
76      resolve(null);
77    }, time);
78  });
79}
80
81export async function test(t) {
82  t.describe('MediaLibrary', async () => {
83    let testAssets;
84    let album;
85    let files;
86
87    t.beforeAll(async () => {
88      await Permissions.askAsync(Permissions.CAMERA_ROLL);
89      files = await getFiles();
90      testAssets = await getAssets(files);
91      album = await MediaLibrary.getAlbumAsync(ALBUM_NAME);
92      if (album == null) album = await createAlbum(testAssets, ALBUM_NAME);
93      else await MediaLibrary.addAssetsToAlbumAsync(testAssets, album, true);
94    });
95
96    t.describe('Every return value has proper shape', async () => {
97      t.it('createAssetAsync', () => {
98        const keys = Object.keys(testAssets[0]);
99        ASSET_KEYS.forEach(key => t.expect(keys).toContain(key));
100      });
101
102      t.it('getAssetInfoAsync', async () => {
103        const { assets } = await MediaLibrary.getAssetsAsync();
104        const value = await MediaLibrary.getAssetInfoAsync(assets[0]);
105        const keys = Object.keys(value);
106        INFO_KEYS.forEach(key => t.expect(keys).toContain(key));
107      });
108
109      t.it('getAlbumAsync', async () => {
110        const value = await MediaLibrary.getAlbumAsync(ALBUM_NAME);
111        const keys = Object.keys(value);
112        ALBUM_KEYS.forEach(key => t.expect(keys).toContain(key));
113      });
114
115      t.it('getAssetsAsync', async () => {
116        const value = await MediaLibrary.getAssetsAsync();
117        const keys = Object.keys(value);
118        GET_ASSETS_KEYS.forEach(key => t.expect(keys).toContain(key));
119      });
120    });
121
122    t.describe('Small tests', async () => {
123      t.it('Function getAlbums returns test album', async () => {
124        const albums = await MediaLibrary.getAlbumsAsync();
125        t.expect(albums.filter(elem => elem.id === album.id).length).toBe(1);
126      });
127
128      t.it('getAlbum returns test album', async () => {
129        const otherAlbum = await MediaLibrary.getAlbumAsync(ALBUM_NAME);
130        t.expect(otherAlbum.title).toBe(album.title);
131        t.expect(otherAlbum.id).toBe(album.id);
132        t.expect(otherAlbum.assetCount).toBe(F_SIZE);
133      });
134
135      t.it('getAlbum with not existing album', async () => {
136        const album = await MediaLibrary.getAlbumAsync(WRONG_NAME);
137        t.expect(album).toBeNull();
138      });
139
140      t.it('getAssetInfo with not existing id', async () => {
141        const asset = await MediaLibrary.getAssetInfoAsync(WRONG_ID);
142        t.expect(asset).toBeNull();
143      });
144
145      // On both platforms assets should perserve their id. On iOS it's native behaviour,
146      // but on Android it should be implemented (but it isn't)
147      // t.it("After createAlbum and addAssetsTo album all assets have the same id", async () => {
148      //   await Promise.all(testAssets.map(async asset => {
149      //     const info = await MediaLibrary.getAssetInfoAsync(asset);
150      //     t.expect(info.id).toBe(asset.id);
151      //   }));
152      // });
153    });
154
155    t.describe('getAssetsAsync', async () => {
156      t.it('No arguments', async () => {
157        const options = {};
158        const { assets } = await MediaLibrary.getAssetsAsync(options);
159        t.expect(assets.length).toBeLessThanOrEqual(DEFAULT_PAGE_SIZE);
160        t.expect(assets.length).toBeGreaterThanOrEqual(IMG_NUMBER);
161        assets.forEach(asset => t.expect(DEFAULT_MEDIA_TYPES).toContain(asset.mediaType));
162      });
163
164      t.it('album', async () => {
165        const options = { album };
166        const { assets } = await MediaLibrary.getAssetsAsync(options);
167        t.expect(assets.length).toBe(IMG_NUMBER);
168        assets.forEach(asset => t.expect(DEFAULT_MEDIA_TYPES).toContain(asset.mediaType));
169        if (Platform.OS == 'android')
170          assets.forEach(asset => t.expect(asset.albumId).toBe(album.id));
171      });
172
173      t.it('first, after', async () => {
174        const options = { first: 2, album };
175        {
176          const { assets, endCursor, hasNextPage, totalCount } = await MediaLibrary.getAssetsAsync(
177            options
178          );
179          t.expect(assets.length).toBe(2);
180          t.expect(totalCount).toBe(IMG_NUMBER);
181          t.expect(hasNextPage).toBeTruthy();
182          assets.forEach(asset => t.expect(DEFAULT_MEDIA_TYPES).toContain(asset.mediaType));
183          options.after = endCursor;
184        }
185        {
186          const { assets, hasNextPage, totalCount } = await MediaLibrary.getAssetsAsync(options);
187          t.expect(assets.length).toBe(IMG_NUMBER - 2);
188          t.expect(totalCount).toBe(IMG_NUMBER);
189          t.expect(hasNextPage).toBeFalsy();
190        }
191      });
192
193      t.it('mediaType: video', async () => {
194        const mediaType = MediaLibrary.MediaType.video;
195        const options = { mediaType, album };
196        const { assets } = await MediaLibrary.getAssetsAsync(options);
197        assets.forEach(asset => t.expect(asset.mediaType).toBe(mediaType));
198        t.expect(assets.length).toBe(1);
199      });
200
201      t.it('mediaType: photo', async () => {
202        const mediaType = MediaLibrary.MediaType.photo;
203        const options = { mediaType, album };
204        const { assets } = await MediaLibrary.getAssetsAsync(options);
205        t.expect(assets.length).toBe(IMG_NUMBER);
206        assets.forEach(asset => t.expect(asset.mediaType).toBe(mediaType));
207      });
208    });
209
210    t.describe('Delete tests', async () => {
211      t.it('deleteAssetsAsync', async () => {
212        const { assets } = await MediaLibrary.getAssetsAsync({ album, mediaType: MEDIA_TYPES });
213        const result = await MediaLibrary.deleteAssetsAsync(assets.slice(0, 2));
214        const { assets: rest } = await MediaLibrary.getAssetsAsync({
215          album,
216          mediaType: MEDIA_TYPES,
217        });
218        t.expect(result).toEqual(true);
219        t.expect(rest.length).toBe(F_SIZE - 2);
220      });
221      t.it('deleteAlbumsAsync', async () => {
222        const result = await MediaLibrary.deleteAlbumsAsync(album, true);
223        t.expect(result).toEqual(true);
224        album = await MediaLibrary.getAlbumAsync(ALBUM_NAME);
225        t.expect(album).toBeNull();
226      });
227      t.it('deleteManyAlbums', async () => {
228        const assets = await getAssets(files.slice(0, 2));
229        let firstAlbum = await MediaLibrary.createAlbumAsync(ALBUM_NAME, assets[0], false);
230        let secondAlbum = await MediaLibrary.createAlbumAsync(SECOND_ALBUM_NAME, assets[1], false);
231        await MediaLibrary.deleteAlbumsAsync([firstAlbum, secondAlbum], true);
232        firstAlbum = await MediaLibrary.getAlbumAsync(ALBUM_NAME);
233        secondAlbum = await MediaLibrary.getAlbumAsync(SECOND_ALBUM_NAME);
234        const firstAsset = await MediaLibrary.getAssetInfoAsync(assets[0]);
235        const secondAsset = await MediaLibrary.getAssetInfoAsync(assets[1]);
236        t.expect(firstAlbum).toBeNull();
237        t.expect(secondAlbum).toBeNull();
238        t.expect(firstAsset).toBeNull();
239        t.expect(secondAsset).toBeNull();
240      });
241    });
242
243    t.describe('Listeners', async () => {
244      t.it('addAsset calls listener', async () => {
245        const spy = t.jasmine.createSpy('addAsset spy', () => {});
246        const remove = MediaLibrary.addListener(spy);
247        const asset = await MediaLibrary.createAssetAsync(files[0].localUri);
248        await timeoutWrapper(() => t.expect(spy).toHaveBeenCalled(), WAIT_TIME);
249        remove.remove();
250        await MediaLibrary.deleteAssetsAsync(asset);
251      });
252
253      t.it('remove listener', async () => {
254        const spy = t.jasmine.createSpy('remove spy', () => {});
255        const subscription = MediaLibrary.addListener(spy);
256        subscription.remove();
257        const asset = await MediaLibrary.createAssetAsync(files[0].localUri);
258        await MediaLibrary.deleteAssetsAsync(asset);
259        await timeoutWrapper(() => t.expect(spy).not.toHaveBeenCalled(), WAIT_TIME);
260      });
261
262      t.it('deleteListener calls listener', async () => {
263        const spy = t.jasmine.createSpy('deleteAsset spy', () => {});
264        const asset = await MediaLibrary.createAssetAsync(files[0].localUri);
265        const subscription = MediaLibrary.addListener(spy);
266        await MediaLibrary.deleteAssetsAsync(asset);
267        await timeoutWrapper(() => t.expect(spy).toHaveBeenCalled(), WAIT_TIME);
268        subscription.remove();
269      });
270
271      t.it('removeAllListeners', async () => {
272        const spy = t.jasmine.createSpy('removeAll', () => {});
273        MediaLibrary.addListener(spy);
274        MediaLibrary.removeAllListeners();
275        const asset = await MediaLibrary.createAssetAsync(files[0].localUri);
276        await timeoutWrapper(() => t.expect(spy).not.toHaveBeenCalled(), WAIT_TIME);
277        await MediaLibrary.deleteAssetsAsync(asset);
278      });
279    });
280  });
281}
282