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