Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / packages / shared / test / contacts / vcard.spec.ts
blobf4d3343a8f8ed5aee9bd493e6c8bef371ea106b1
1 import { createContactPropertyUid } from '@proton/shared/lib/contacts/properties';
2 import { parseToVCard, serialize } from '@proton/shared/lib/contacts/vcard';
3 import type { VCardContact } from '@proton/shared/lib/interfaces/contacts/VCard';
5 describe('serialize', () => {
6     describe('produces the expected vcf', () => {
7         it('when there are all supported standard properties', () => {
8             const contact: VCardContact = {
9                 version: { field: 'version', value: '4.0', uid: createContactPropertyUid() },
10                 n: {
11                     field: 'n',
12                     value: {
13                         familyNames: ['Messi'],
14                         givenNames: ['Lionel'],
15                         additionalNames: ['Andrés'],
16                         honorificPrefixes: [''],
17                         honorificSuffixes: [''],
18                     },
19                     uid: createContactPropertyUid(),
20                 },
21                 fn: [{ field: 'fn', value: 'Leo Messi', uid: createContactPropertyUid() }],
22                 title: [{ field: 'title', value: 'Football ambassador', uid: createContactPropertyUid() }],
23                 nickname: [
24                     { field: 'nickname', value: 'La Pulga', uid: createContactPropertyUid() },
25                     { field: 'nickname', value: 'The Messiah', uid: createContactPropertyUid() },
26                 ],
27                 email: [
28                     {
29                         field: 'email',
30                         value: 'leo@barca.cat',
31                         params: { type: 'WORK', pref: '1' },
32                         group: 'item1',
33                         uid: createContactPropertyUid(),
34                     },
35                 ],
36                 adr: [
37                     {
38                         field: 'adr',
39                         value: {
40                             postOfficeBox: '',
41                             extendedAddress: '',
42                             streetAddress: "Carrer d'Elisabeth Eidenbenz",
43                             locality: 'Barcelona',
44                             region: 'Catalunya',
45                             postalCode: '08028',
46                             country: 'Catalonia',
47                         },
48                         uid: createContactPropertyUid(),
49                     },
50                 ],
51                 tel: [
52                     {
53                         field: 'tel',
54                         value: 'tel:+1-555-555-5555;ext=5555',
55                         params: {
56                             value: 'uri',
57                             type: 'voice',
58                         },
59                         uid: createContactPropertyUid(),
60                     },
61                 ],
62                 bday: {
63                     field: 'bday',
64                     value: {
65                         date: new Date(Date.UTC(1999, 5, 9)),
66                     },
67                     uid: createContactPropertyUid(),
68                 },
69                 anniversary: {
70                     field: 'anniversary',
71                     value: {
72                         text: 'ferragosto',
73                     },
74                     uid: createContactPropertyUid(),
75                 },
76                 impp: [
77                     {
78                         field: 'impp',
79                         value: 'xmpp:leo@barca.cat',
80                         params: { pref: '1' },
81                         uid: createContactPropertyUid(),
82                     },
83                 ],
84                 photo: [{ field: 'photo', value: 'bcspevahwseowngfs23dkl1lsxslfj', uid: createContactPropertyUid() }],
85                 lang: [
86                     {
87                         field: 'lang',
88                         params: { type: 'home', pref: '1' },
89                         value: 'es',
90                         uid: createContactPropertyUid(),
91                     },
92                     {
93                         field: 'lang',
94                         params: { type: 'work', pref: '1' },
95                         value: 'es',
96                         uid: createContactPropertyUid(),
97                     },
98                     {
99                         field: 'lang',
100                         params: { type: 'work', pref: '2' },
101                         value: 'en',
102                         uid: createContactPropertyUid(),
103                     },
104                 ],
105                 geo: [{ field: 'geo', value: 'geo:37.386013,-122.082932', uid: createContactPropertyUid() }],
106                 role: [{ field: 'role', value: 'Fake 9', uid: createContactPropertyUid() }],
107                 logo: [
108                     {
109                         field: 'logo',
110                         value: 'https://static.messi.com/wp-content/uploads/2019/10/messi-logo-01.png',
111                         uid: createContactPropertyUid(),
112                     },
113                 ],
114                 org: [
115                     {
116                         field: 'org',
117                         value: { organizationalName: 'Barcelona FC', organizationalUnitNames: ['Training-center'] },
118                         uid: createContactPropertyUid(),
119                     },
120                     {
121                         field: 'org',
122                         value: { organizationalName: 'Leo Messi Foundation' },
123                         uid: createContactPropertyUid(),
124                     },
125                 ],
126                 member: [{ field: 'member', value: 'tel:+1-418-555-5555', uid: createContactPropertyUid() }],
127                 related: [
128                     {
129                         field: 'related',
130                         params: { type: 'spouse' },
131                         value: 'Antonela Roccuzzo',
132                         uid: createContactPropertyUid(),
133                     },
134                     {
135                         field: 'related',
136                         params: { type: 'child' },
137                         value: 'Matteo Messi Roccuzzo',
138                         uid: createContactPropertyUid(),
139                     },
140                     {
141                         field: 'related',
142                         params: { type: 'child' },
143                         value: 'Thiago Messi Roccuzzo',
144                         uid: createContactPropertyUid(),
145                     },
146                     {
147                         field: 'related',
148                         params: { type: 'child' },
149                         value: 'Ciro Messi Roccuzzo',
150                         uid: createContactPropertyUid(),
151                     },
152                 ],
153                 note: [{ field: 'note', value: 'Way better than Cristiano Ronaldo!', uid: createContactPropertyUid() }],
154                 url: [{ field: 'url', value: 'https://messi.com/', uid: createContactPropertyUid() }],
155                 categories: [
156                     {
157                         field: 'categories',
158                         value: 'Tax-evading players',
159                         uid: createContactPropertyUid(),
160                     },
161                     {
162                         field: 'categories',
163                         value: 'Best players of all time',
164                         uid: createContactPropertyUid(),
165                     },
166                 ],
167             };
168             const vcf = [
169                 `BEGIN:VCARD`,
170                 `VERSION:4.0`,
171                 `FN:Leo Messi`,
172                 `N:Messi;Lionel;Andrés;;`,
173                 `TITLE:Football ambassador`,
174                 `NICKNAME:La Pulga`,
175                 `NICKNAME:The Messiah`,
176                 `ITEM1.EMAIL;TYPE=WORK;PREF=1:leo@barca.cat`,
177                 `ADR:;;Carrer d'Elisabeth Eidenbenz;Barcelona;Catalunya;08028;Catalonia`,
178                 `TEL;VALUE=uri;TYPE=voice:tel:+1-555-555-5555;ext=5555`,
179                 `BDAY:19990609`,
180                 `ANNIVERSARY;VALUE=TEXT:ferragosto`,
181                 `IMPP;PREF=1:xmpp:leo@barca.cat`,
182                 `PHOTO:bcspevahwseowngfs23dkl1lsxslfj`,
183                 `LANG;TYPE=home;PREF=1:es`,
184                 `LANG;TYPE=work;PREF=1:es`,
185                 `LANG;TYPE=work;PREF=2:en`,
186                 `GEO;VALUE=FLOAT:geo:37.386013,-122.082932`,
187                 `ROLE:Fake 9`,
188                 `LOGO:https://static.messi.com/wp-content/uploads/2019/10/messi-logo-01.png`,
189                 `ORG:Barcelona FC;Training-center`,
190                 `ORG:Leo Messi Foundation`,
191                 `MEMBER:tel:+1-418-555-5555`,
192                 `RELATED;TYPE=spouse:Antonela Roccuzzo`,
193                 `RELATED;TYPE=child:Matteo Messi Roccuzzo`,
194                 `RELATED;TYPE=child:Thiago Messi Roccuzzo`,
195                 `RELATED;TYPE=child:Ciro Messi Roccuzzo`,
196                 `NOTE:Way better than Cristiano Ronaldo!`,
197                 `URL:https://messi.com/`,
198                 `CATEGORIES:Tax-evading players`,
199                 `CATEGORIES:Best players of all time`,
200                 `END:VCARD`,
201             ].join('\r\n');
203             expect(serialize(contact)).toEqual(vcf);
204         });
206         it('when there are both categories with vcard group and without', () => {
207             const contact: VCardContact = {
208                 version: { field: 'version', value: '4.0', uid: createContactPropertyUid() },
209                 fn: [{ field: 'fn', value: 'dummy', uid: createContactPropertyUid() }],
210                 categories: [
211                     { field: 'categories', value: 'first', uid: createContactPropertyUid() },
212                     {
213                         field: 'categories',
214                         group: 'item1',
215                         value: 'second',
216                         uid: createContactPropertyUid(),
217                     },
218                     {
219                         field: 'categories',
220                         group: 'item1',
221                         value: 'third',
222                         uid: createContactPropertyUid(),
223                     },
224                 ],
225             };
226             const vcf = [
227                 `BEGIN:VCARD`,
228                 `VERSION:4.0`,
229                 `FN:dummy`,
230                 `CATEGORIES:first`,
231                 `ITEM1.CATEGORIES:second`,
232                 `ITEM1.CATEGORIES:third`,
233                 `END:VCARD`,
234             ].join('\r\n');
236             expect(serialize(contact)).toEqual(vcf);
237         });
239         it('unexpectedly prints FLOAT for the GEO property', () => {
240             const contact: VCardContact = {
241                 version: { field: 'version', value: '4.0', uid: createContactPropertyUid() },
242                 fn: [{ field: 'fn', value: 'dummy', uid: createContactPropertyUid() }],
243                 geo: [{ field: 'geo', value: 'geo:41,34', uid: createContactPropertyUid() }],
244             };
245             const vcf = [`BEGIN:VCARD`, `VERSION:4.0`, `FN:dummy`, `GEO;VALUE=FLOAT:geo:41,34`, `END:VCARD`].join(
246                 '\r\n'
247             );
249             expect(serialize(contact)).toEqual(vcf);
250         });
252         it('no value is passed to FN field', () => {
253             const contact = {
254                 version: { field: 'version', value: '4.0', uid: createContactPropertyUid() },
255                 fn: [{ field: 'fn', value: '', uid: createContactPropertyUid(), params: { pref: '1' } }],
256             };
258             expect(serialize(contact)).toEqual([`BEGIN:VCARD`, `VERSION:4.0`, `END:VCARD`].join('\r\n'));
259         });
260     });
262     describe('round trips with parse', () => {
263         it('for a simple vcard', () => {
264             const vcf = [
265                 `BEGIN:VCARD`,
266                 `VERSION:4.0`,
267                 // `FN;PID=1.1:J. Doe`,
268                 // `UID:urn:uuid:4fbe8971-0bc3-424c-9c26-36c3e1eff6b1`,
269                 `N:Doe;J.;;;`,
270                 // `EMAIL;PID=1.1:jdoe@example.com`,
271                 // `EMAIL:jdoeeeee@example.com`,
272                 // `CLIENTPIDMAP:1;urn:uuid:53e374d9-337e-4727-8803-a1e9c14e0556`,
273                 // `CATEGORIES:TRAVEL AGENT`,
274                 // `CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY`,
275                 `END:VCARD`,
276             ].join('\r\n');
278             expect(serialize(parseToVCard(vcf))).toEqual(vcf);
279         });
281         it('always puts FN after version', () => {
282             const vcf = [
283                 `BEGIN:VCARD`,
284                 `VERSION:4.0`,
285                 `UID:urn:uuid:4fbe8971-0bc3-424c-9c26-36c3e1eff6b1`,
286                 `N:Doe;J.;;;`,
287                 `EMAIL;PID=1.1:jdoe@example.com`,
288                 `EMAIL:jdoeeeee@example.com`,
289                 `CLIENTPIDMAP:1;urn:uuid:53e374d9-337e-4727-8803-a1e9c14e0556`,
290                 `FN;PID=1.1:J. Doe`,
291                 `CATEGORIES:TRAVEL AGENT`,
292                 `CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY`,
293                 `END:VCARD`,
294             ].join('\r\n');
295             const expected = [
296                 `BEGIN:VCARD`,
297                 `VERSION:4.0`,
298                 `FN;PID=1.1:J. Doe`,
299                 `UID:urn:uuid:4fbe8971-0bc3-424c-9c26-36c3e1eff6b1`,
300                 `N:Doe;J.;;;`,
301                 `EMAIL;PID=1.1:jdoe@example.com`,
302                 `EMAIL:jdoeeeee@example.com`,
303                 `CLIENTPIDMAP:1;urn:uuid:53e374d9-337e-4727-8803-a1e9c14e0556`,
304                 `CATEGORIES:TRAVEL AGENT`,
305                 `CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY`,
306                 `END:VCARD`,
307             ].join('\r\n');
309             expect(serialize(parseToVCard(vcf))).toEqual(expected);
310         });
311     });
314 describe('parseToVcard', () => {
315     describe('parses correctly the N property', () => {
316         it('when it contains all components and single values', () => {
317             const vcf = [`BEGIN:VCARD`, `VERSION:4.0`, `N:Public;John;Quinlan;Mr.;Esq.`, `END:VCARD`].join('\r\n');
319             const result = parseToVCard(vcf);
320             expect(result.n?.value).toEqual({
321                 familyNames: ['Public'],
322                 givenNames: ['John'],
323                 additionalNames: ['Quinlan'],
324                 honorificPrefixes: ['Mr.'],
325                 honorificSuffixes: ['Esq.'],
326             });
327         });
329         it('when it contains all components and multiple values', () => {
330             const vcf = [
331                 `BEGIN:VCARD`,
332                 `VERSION:4.0`,
333                 `N:Stevenson;John;Philip,Paul;Dr.;Jr.,M.D.,A.C.P.`,
334                 `END:VCARD`,
335             ].join('\r\n');
337             const result = parseToVCard(vcf);
338             expect(result.n?.value).toEqual({
339                 familyNames: ['Stevenson'],
340                 givenNames: ['John'],
341                 additionalNames: ['Philip', 'Paul'],
342                 honorificPrefixes: ['Dr.'],
343                 honorificSuffixes: ['Jr.', 'M.D.', 'A.C.P.'],
344             });
345         });
347         it('when it contains all components and some are empty', () => {
348             const vcf = [`BEGIN:VCARD`, `VERSION:4.0`, `N:Yamada;Taro;;;`, `END:VCARD`].join('\r\n');
350             const result = parseToVCard(vcf);
351             expect(result.n?.value).toEqual({
352                 familyNames: ['Yamada'],
353                 givenNames: ['Taro'],
354                 additionalNames: [''],
355                 honorificPrefixes: [''],
356                 honorificSuffixes: [''],
357             });
358         });
359     });
361     describe('can parse a vcard with an invalid N property', () => {
362         it('if the N property has no semicolon at all', () => {
363             const vcf = [`BEGIN:VCARD`, `VERSION:4.0`, `FN:Santa Claus`, `N:Santa Claus`, `END:VCARD`].join('\r\n');
365             const result = parseToVCard(vcf);
366             expect(result.fn[0].value).toBe('Santa Claus');
367             expect(result.n?.value).toEqual({
368                 familyNames: ['Santa Claus'],
369                 givenNames: [''],
370                 additionalNames: [''],
371                 honorificPrefixes: [''],
372                 honorificSuffixes: [''],
373             });
374         });
376         it('if the N property has an incorrect number of semicolons', () => {
377             const vcf = [`BEGIN:VCARD`, `VERSION:4.0`, `FN:Santa Claus`, `N:Claus;Santa`, `END:VCARD`].join('\r\n');
379             const result = parseToVCard(vcf);
380             expect(result.fn[0].value).toBe('Santa Claus');
381             expect(result.n?.value).toEqual({
382                 familyNames: ['Claus'],
383                 givenNames: ['Santa'],
384                 additionalNames: [''],
385                 honorificPrefixes: [''],
386                 honorificSuffixes: [''],
387             });
388         });
389     });