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