xref: /expo/apps/test-suite/tests/Contacts.js (revision f10e66fc)
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