1'use strict'; 2 3import { Contacts, Permissions } from 'expo'; 4import { Platform } from 'react-native'; 5import * as TestUtils from '../TestUtils'; 6 7export const name = 'Contacts'; 8 9async function sortContacts(expect, sortField) { 10 const { data: contacts } = await Contacts.getContactsAsync({ 11 fields: [sortField], 12 sort: sortField, 13 pageOffset: 0, 14 pageSize: 5, 15 }); 16 17 for (let i = 1; i < contacts.length; i++) { 18 const { [sortField]: propA } = contacts[i - 1]; 19 const { [sortField]: propB } = contacts[i]; 20 if (propA && propB) { 21 const order = propA.localeCompare(propB); 22 expect(Math.max(order, 0)).toBe(0); 23 } 24 } 25} 26 27export async function test({ describe, it, xdescribe, jasmine, expect }) { 28 const shouldSkipTestsRequiringPermissions = await TestUtils.shouldSkipTestsRequiringPermissionsAsync(); 29 const describeWithPermissions = shouldSkipTestsRequiringPermissions ? xdescribe : describe; 30 31 describeWithPermissions('Contacts', () => { 32 let firstContact; 33 const isAndroid = Platform.OS !== 'ios'; 34 const testGroupName = 'Test Expo Contacts'; 35 let firstGroup; 36 let testGroups = []; 37 38 if (Platform.OS === 'ios') { 39 let customContactId; 40 describe('Contacts.addContactAsync()', () => { 41 it('creates contact', async () => { 42 customContactId = await Contacts.addContactAsync({ 43 [Contacts.Fields.FirstName]: 'Eric', 44 [Contacts.Fields.LastName]: 'Cartman', 45 [Contacts.Fields.JobTitle]: 'Actor', 46 }); 47 expect(typeof customContactId).toBe('string'); 48 }); 49 }); 50 51 describe('Contacts.writeContactToFileAsync()', () => { 52 it('returns uri', async () => { 53 const localUri = await Contacts.writeContactToFileAsync({ id: customContactId }); 54 55 expect(typeof localUri).toBe('string'); 56 }); 57 }); 58 59 describe('Contacts.removeContactAsync()', () => { 60 it('removes contact', async () => { 61 let errorMessage; 62 try { 63 await Contacts.removeContactAsync(customContactId); 64 } catch ({ message }) { 65 errorMessage = message; 66 } 67 expect(errorMessage).toBeUndefined(); 68 }); 69 }); 70 } 71 72 describe('Contacts.getContactsAsync()', () => { 73 it('gets permission and at least one result, all results of right shape', async () => { 74 await TestUtils.acceptPermissionsAndRunCommandAsync(() => { 75 return Permissions.askAsync(Permissions.CONTACTS); 76 }); 77 let contacts = await Contacts.getContactsAsync({ 78 fields: [Contacts.Fields.PhoneNumbers, Contacts.Fields.Emails], 79 pageSize: 1, 80 }); 81 expect(contacts.data.length > 0).toBe(true); 82 contacts.data.forEach(({ id, name, phoneNumbers, emails }) => { 83 expect(typeof id === 'string' || typeof id === 'number').toBe(true); 84 expect(typeof name === 'string' || typeof name === 'undefined').toBe(true); 85 expect(Array.isArray(phoneNumbers) || typeof phoneNumbers === 'undefined').toBe(true); 86 expect(Array.isArray(emails) || typeof emails === 'undefined').toBe(true); 87 }); 88 firstContact = contacts.data[0]; 89 }); 90 91 it('returns correct shape', async () => { 92 const { 93 data, 94 hasNextPage, 95 hasPreviousPage, 96 total, 97 ...props 98 } = await Contacts.getContactsAsync({ 99 pageSize: 1, 100 }); 101 102 // Test some constant values 103 104 expect(data).toBeDefined(); 105 106 expect(typeof hasNextPage).toBe('boolean'); 107 expect(typeof hasPreviousPage).toBe('boolean'); 108 expect(typeof total).toBe('number'); 109 110 expect(data.length).toBe(1); 111 expect(hasPreviousPage).toBe(false); 112 //This could fail, if the device only has 1 contact 113 expect(hasNextPage).toBe(true); 114 //This could fail, if the device only has 1 contact 115 expect(total).toBeGreaterThan(1); 116 117 // Nothing else. 118 expect(Object.keys(props).length).toBe(0); 119 120 // Test a contact 121 expect(data[0]).toEqual( 122 jasmine.objectContaining({ 123 contactType: jasmine.any(String), 124 name: jasmine.any(String), 125 id: jasmine.any(String), 126 }) 127 ); 128 expect(data[0].imageAvailable).toBeDefined(); 129 }); 130 131 async function getContactAtIndex(index, fields) { 132 // Make sure the contact has a phone number to skip! 133 const { data } = await Contacts.getContactsAsync({ 134 fields, 135 pageSize: 1, 136 pageOffset: index, 137 }); 138 return data[0]; 139 } 140 141 it('skips phone number if not asked', async () => { 142 // This may need to be tweaked because you cannot add test contacts on android 143 const testIndex = 1; 144 const initialContact = await getContactAtIndex(testIndex, [Contacts.Fields.PhoneNumbers]); 145 expect(initialContact.phoneNumbers).toBeDefined(); 146 expect(initialContact.phoneNumbers.length).toBeGreaterThan(0); 147 expect(initialContact.phoneNumbers[0]).toEqual( 148 jasmine.objectContaining({ 149 id: jasmine.any(String), 150 label: jasmine.any(String), 151 number: jasmine.any(String), 152 }) 153 ); 154 155 const initialContactWithoutNumbers = await getContactAtIndex(testIndex, []); 156 expect(initialContactWithoutNumbers.phoneNumbers).toBeUndefined(); 157 }); 158 159 it('gets a local image', async () => { 160 // This may need to be tweaked because you cannot add test contacts on android 161 const contact = await getContactAtIndex(0, [Contacts.Fields.Image, 'imageBase64']); 162 expect(contact.imageAvailable).toBe(true); 163 expect(contact.thumbnail).toBeUndefined(); 164 165 if (isAndroid) { 166 expect(contact.image).toEqual( 167 jasmine.objectContaining({ 168 uri: jasmine.any(String), 169 }) 170 ); 171 } else { 172 expect(contact.image).toEqual( 173 jasmine.objectContaining({ 174 uri: jasmine.any(String), 175 height: jasmine.any(Number), 176 width: jasmine.any(Number), 177 base64: jasmine.any(String), 178 }) 179 ); 180 } 181 }); 182 183 it('respects the page size', async () => { 184 const contacts = await Contacts.getContactsAsync({ 185 fields: [], 186 pageOffset: 0, 187 pageSize: 2, 188 }); 189 expect(contacts.data.length).toBeLessThan(3); 190 }); 191 192 if (Platform.OS === 'android') { 193 it('sorts contacts by first name', async () => { 194 await sortContacts(expect, Contacts.SortTypes.FirstName); 195 }); 196 it('sorts contacts by last name', async () => { 197 await sortContacts(expect, Contacts.SortTypes.LastName); 198 }); 199 } 200 201 it('respects the page offset', async () => { 202 const firstPage = await Contacts.getContactsAsync({ 203 fields: [Contacts.Fields.PhoneNumbers], 204 pageOffset: 0, 205 pageSize: 2, 206 }); 207 const secondPage = await Contacts.getContactsAsync({ 208 fields: [Contacts.Fields.PhoneNumbers], 209 pageOffset: 1, 210 pageSize: 2, 211 }); 212 213 if (firstPage.data.length >= 3) { 214 expect(firstPage.data.length).toBe(2); 215 expect(secondPage.data.length).toBe(2); 216 expect(firstPage.data[0].id).not.toBe(secondPage.data[0].id); 217 expect(firstPage.data[1].id).not.toBe(secondPage.data[1].id); 218 expect(firstPage.data[1].id).toBe(secondPage.data[0].id); 219 } 220 }); 221 }); 222 223 describe('Contacts.getContactByIdAsync()', () => { 224 it('gets a result of right shape', async () => { 225 if (firstContact) { 226 const contact = await Contacts.getContactByIdAsync(firstContact.id, [ 227 Contacts.Fields.PhoneNumbers, 228 Contacts.Fields.Emails, 229 ]); 230 const { id, name, phoneNumbers, emails } = contact; 231 232 expect(contact.note).toBeUndefined(); 233 expect(contact.relationships).toBeUndefined(); 234 expect(contact.addresses).toBeUndefined(); 235 236 expect(phoneNumbers[0]).toEqual( 237 jasmine.objectContaining({ 238 id: jasmine.any(String), 239 label: jasmine.any(String), 240 number: jasmine.any(String), 241 }) 242 ); 243 244 expect(contact).toEqual( 245 jasmine.objectContaining({ 246 contactType: jasmine.any(String), 247 name: jasmine.any(String), 248 id: jasmine.any(String), 249 }) 250 ); 251 expect(contact.imageAvailable).toBeDefined(); 252 expect(Array.isArray(emails) || typeof emails === 'undefined').toBe(true); 253 } 254 }); 255 }); 256 257 describe('Contacts.createGroupAsync()', () => { 258 it(`creates a group named ${testGroupName}`, async () => { 259 let errorMessage; 260 let groupId; 261 try { 262 groupId = await Contacts.createGroupAsync(testGroupName); 263 } catch ({ message }) { 264 errorMessage = message; 265 } finally { 266 if (isAndroid) { 267 expect(errorMessage).toBe('Error: Contacts.createGroupAsync: iOS Only'); 268 } else { 269 expect(typeof groupId).toBe('string'); 270 } 271 } 272 }); 273 }); 274 275 describe('Contacts.getGroupsAsync()', () => { 276 it('gets all groups', async () => { 277 let errorMessage; 278 let groups; 279 try { 280 groups = await Contacts.getGroupsAsync({}); 281 firstGroup = groups[0]; 282 } catch ({ message }) { 283 errorMessage = message; 284 } finally { 285 if (isAndroid) { 286 expect(errorMessage).toBe('Error: Contacts.getGroupsAsync: iOS Only'); 287 } else { 288 expect(Array.isArray(groups)).toBe(true); 289 expect(groups.length).toBeGreaterThan(0); 290 } 291 } 292 }); 293 it(`gets groups named "${testGroupName}"`, async () => { 294 let errorMessage; 295 const groupName = testGroupName; 296 let groups; 297 try { 298 groups = await Contacts.getGroupsAsync({ 299 groupName, 300 }); 301 testGroups = groups; 302 } catch ({ message }) { 303 errorMessage = message; 304 } finally { 305 if (isAndroid) { 306 expect(errorMessage).toBe('Error: Contacts.getGroupsAsync: iOS Only'); 307 } else { 308 expect(Array.isArray(groups)).toBe(true); 309 expect(groups.length).toBeGreaterThan(0); 310 311 for (const group of groups) { 312 expect(group.name).toBe(groupName); 313 } 314 } 315 } 316 }); 317 it('gets groups in default container', async () => { 318 let errorMessage; 319 let groups; 320 try { 321 const containerId = await Contacts.getDefaultContainerIdAsync(); 322 groups = await Contacts.getGroupsAsync({ 323 containerId, 324 }); 325 } catch ({ message }) { 326 errorMessage = message; 327 } finally { 328 if (isAndroid) { 329 expect(errorMessage).toBe('Error: Contacts.getDefaultContainerIdAsync: iOS Only'); 330 } else { 331 expect(Array.isArray(groups)).toBe(true); 332 expect(groups.length).toBeGreaterThan(0); 333 } 334 } 335 }); 336 337 if (!isAndroid) { 338 it('gets group with ID', async () => { 339 const groups = await Contacts.getGroupsAsync({ 340 groupId: firstGroup.id, 341 }); 342 expect(Array.isArray(groups)).toBe(true); 343 expect(groups.length).toBe(1); 344 expect(groups[0].id).toBe(firstGroup.id); 345 }); 346 } 347 }); 348 349 describe('Contacts.removeGroupAsync()', () => { 350 it(`remove all groups named ${testGroupName}`, async () => { 351 if (isAndroid) { 352 let errorMessage; 353 try { 354 const success = await Contacts.removeGroupAsync('some-value'); 355 } catch ({ message }) { 356 errorMessage = message; 357 } finally { 358 expect(errorMessage).toBe('Error: Contacts.removeGroupAsync: iOS Only'); 359 } 360 } else { 361 for (let group of testGroups) { 362 let errorMessage; 363 try { 364 await Contacts.removeGroupAsync(group.id); 365 } catch ({ message }) { 366 errorMessage = message; 367 } 368 expect(errorMessage).toBeUndefined(); 369 } 370 } 371 }); 372 }); 373 374 describe('Contacts.getDefaultContainerIdAsync()', () => { 375 it('default container exists', async () => { 376 let errorMessage; 377 let defaultContainerId; 378 try { 379 defaultContainerId = await Contacts.getDefaultContainerIdAsync(); 380 } catch ({ message }) { 381 errorMessage = message; 382 } finally { 383 if (isAndroid) { 384 expect(errorMessage).toBe('Error: Contacts.getDefaultContainerIdAsync: iOS Only'); 385 } else { 386 expect(typeof defaultContainerId).toBe('string'); 387 } 388 } 389 }); 390 }); 391 }); 392} 393