cozy-sharing 28.11.1 → 28.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,20 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [28.11.3](https://github.com/cozy/cozy-libs/compare/cozy-sharing@28.11.2...cozy-sharing@28.11.3) (2026-03-23)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **cozy-sharing:** Accumulate recipients in federated folder sharing ([a626ee4](https://github.com/cozy/cozy-libs/commit/a626ee4793486d1dcdb5e419a59f39caee6f09f7))
11
+ - **cozy-sharing:** Normalize recipient id in dedup helper ([6bd9292](https://github.com/cozy/cozy-libs/commit/6bd9292ef50f01307f6bfb335812dfc4ecce5c25))
12
+ - Remove warning on cozy-flags missing dependency ([25f9864](https://github.com/cozy/cozy-libs/commit/25f98641a5f9c1f22fa759abe7382d422d4567ef))
13
+
14
+ ## [28.11.2](https://github.com/cozy/cozy-libs/compare/cozy-sharing@28.11.1...cozy-sharing@28.11.2) (2026-03-18)
15
+
16
+ ### Bug Fixes
17
+
18
+ - **cozy-sharing:** Force text primary color on autosuggest text ([0a20cda](https://github.com/cozy/cozy-libs/commit/0a20cda336d1aa5c3b25ffcd9423ecf6c2cddd9c))
19
+
6
20
  ## [28.11.1](https://github.com/cozy/cozy-libs/compare/cozy-sharing@28.11.0...cozy-sharing@28.11.1) (2026-03-17)
7
21
 
8
22
  **Note:** Version bump only for package cozy-sharing
@@ -10,7 +10,7 @@ import { useI18n } from 'twake-i18n';
10
10
  import { DumbFederatedFolderModal } from './DumbFederatedFolderModal';
11
11
  import withLocales from '../../hoc/withLocales';
12
12
  import { useSharingContext } from '../../hooks/useSharingContext';
13
- import { formatRecipients, moveRecipientToReadWrite, moveRecipientToReadOnly, RECIPIENT_INDEX_PREFIX } from '../SharedDrive/helpers';
13
+ import { formatRecipients, mergeAndDeduplicateRecipients, moveRecipientToReadWrite, moveRecipientToReadOnly, RECIPIENT_INDEX_PREFIX } from '../SharedDrive/helpers';
14
14
  export var FederatedFolderModal = withLocales(function (_ref) {
15
15
  var onClose = _ref.onClose,
16
16
  existingDocument = _ref.document;
@@ -98,9 +98,11 @@ export var FederatedFolderModal = withLocales(function (_ref) {
98
98
  folderName = _useState6[0];
99
99
 
100
100
  var onShare = function onShare(params) {
101
- setFederatedRecipients({
102
- recipients: params.recipients || [],
103
- readOnlyRecipients: params.readOnlyRecipients || []
101
+ setFederatedRecipients(function (prev) {
102
+ return {
103
+ recipients: mergeAndDeduplicateRecipients([prev.recipients, params.recipients || []]),
104
+ readOnlyRecipients: mergeAndDeduplicateRecipients([prev.readOnlyRecipients, params.readOnlyRecipients || []])
105
+ };
104
106
  });
105
107
  };
106
108
 
@@ -68,15 +68,33 @@ jest.mock('./DumbFederatedFolderModal', function () {
68
68
  return onShare({
69
69
  recipients: [{
70
70
  _id: 'r1',
71
+ id: 'r1',
71
72
  displayName: 'Alice'
72
73
  }],
73
74
  readOnlyRecipients: [{
74
75
  _id: 'r2',
76
+ id: 'r2',
75
77
  displayName: 'Bob'
76
78
  }]
77
79
  });
78
80
  }
79
81
  }, "Share"), /*#__PURE__*/React.createElement("button", {
82
+ "data-testid": "btn-share-other",
83
+ onClick: function onClick() {
84
+ return onShare({
85
+ recipients: [{
86
+ _id: 'r1',
87
+ id: 'r1',
88
+ displayName: 'Alice'
89
+ }, {
90
+ _id: 'r3',
91
+ id: 'r3',
92
+ displayName: 'Charlie'
93
+ }],
94
+ readOnlyRecipients: []
95
+ });
96
+ }
97
+ }, "Share Other"), /*#__PURE__*/React.createElement("button", {
80
98
  "data-testid": "btn-send",
81
99
  onClick: onSend
82
100
  }, "Send"), /*#__PURE__*/React.createElement("button", {
@@ -285,17 +303,49 @@ describe('FederatedFolderModal', function () {
285
303
  }
286
304
  }, _callee5);
287
305
  })));
288
- });
289
- describe('onSend callback', function () {
290
- it('should call share with correct parameters', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6() {
306
+ it('should accumulate recipients when onShare is called multiple times', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee6() {
291
307
  var _setup6, getByTestId;
292
308
 
293
309
  return _regeneratorRuntime.wrap(function _callee6$(_context6) {
294
310
  while (1) switch (_context6.prev = _context6.next) {
295
311
  case 0:
296
- mockShare.mockResolvedValueOnce({});
297
312
  _setup6 = setup(), getByTestId = _setup6.getByTestId;
298
- _context6.next = 4;
313
+ _context6.next = 3;
314
+ return waitFor(function () {
315
+ expect(getByTestId('recipients-count').textContent).toBe('0');
316
+ });
317
+
318
+ case 3:
319
+ fireEvent.click(getByTestId('btn-share'));
320
+ _context6.next = 6;
321
+ return waitFor(function () {
322
+ expect(getByTestId('recipients-count').textContent).toBe('2');
323
+ });
324
+
325
+ case 6:
326
+ fireEvent.click(getByTestId('btn-share-other'));
327
+ _context6.next = 9;
328
+ return waitFor(function () {
329
+ expect(getByTestId('recipients-count').textContent).toBe('3');
330
+ });
331
+
332
+ case 9:
333
+ case "end":
334
+ return _context6.stop();
335
+ }
336
+ }, _callee6);
337
+ })));
338
+ });
339
+ describe('onSend callback', function () {
340
+ it('should call share with correct parameters', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee7() {
341
+ var _setup7, getByTestId;
342
+
343
+ return _regeneratorRuntime.wrap(function _callee7$(_context7) {
344
+ while (1) switch (_context7.prev = _context7.next) {
345
+ case 0:
346
+ mockShare.mockResolvedValueOnce({});
347
+ _setup7 = setup(), getByTestId = _setup7.getByTestId;
348
+ _context7.next = 4;
299
349
  return waitFor(function () {
300
350
  expect(getByTestId('btn-send')).toBeTruthy();
301
351
  });
@@ -303,17 +353,19 @@ describe('FederatedFolderModal', function () {
303
353
  case 4:
304
354
  fireEvent.click(getByTestId('btn-share'));
305
355
  fireEvent.click(getByTestId('btn-send'));
306
- _context6.next = 8;
356
+ _context7.next = 8;
307
357
  return waitFor(function () {
308
358
  expect(mockShare).toHaveBeenCalledWith({
309
359
  description: 'My Test Folder',
310
360
  document: mockDocument,
311
361
  recipients: [{
312
362
  _id: 'r1',
363
+ id: 'r1',
313
364
  displayName: 'Alice'
314
365
  }],
315
366
  readOnlyRecipients: [{
316
367
  _id: 'r2',
368
+ id: 'r2',
317
369
  displayName: 'Bob'
318
370
  }],
319
371
  sharedDrive: true,
@@ -323,26 +375,26 @@ describe('FederatedFolderModal', function () {
323
375
 
324
376
  case 8:
325
377
  case "end":
326
- return _context6.stop();
378
+ return _context7.stop();
327
379
  }
328
- }, _callee6);
380
+ }, _callee7);
329
381
  })));
330
- it('should show success alert and close modal on successful share', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee7() {
331
- var _setup7, getByTestId;
382
+ it('should show success alert and close modal on successful share', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee8() {
383
+ var _setup8, getByTestId;
332
384
 
333
- return _regeneratorRuntime.wrap(function _callee7$(_context7) {
334
- while (1) switch (_context7.prev = _context7.next) {
385
+ return _regeneratorRuntime.wrap(function _callee8$(_context8) {
386
+ while (1) switch (_context8.prev = _context8.next) {
335
387
  case 0:
336
388
  mockShare.mockResolvedValueOnce({});
337
- _setup7 = setup(), getByTestId = _setup7.getByTestId;
338
- _context7.next = 4;
389
+ _setup8 = setup(), getByTestId = _setup8.getByTestId;
390
+ _context8.next = 4;
339
391
  return waitFor(function () {
340
392
  expect(getByTestId('btn-send')).toBeTruthy();
341
393
  });
342
394
 
343
395
  case 4:
344
396
  fireEvent.click(getByTestId('btn-send'));
345
- _context7.next = 7;
397
+ _context8.next = 7;
346
398
  return waitFor(function () {
347
399
  expect(mockShowAlert).toHaveBeenCalledWith({
348
400
  message: 'Folder has been shared',
@@ -354,26 +406,26 @@ describe('FederatedFolderModal', function () {
354
406
 
355
407
  case 7:
356
408
  case "end":
357
- return _context7.stop();
409
+ return _context8.stop();
358
410
  }
359
- }, _callee7);
411
+ }, _callee8);
360
412
  })));
361
- it('should show error alert when share fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee8() {
362
- var _setup8, getByTestId;
413
+ it('should show error alert when share fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee9() {
414
+ var _setup9, getByTestId;
363
415
 
364
- return _regeneratorRuntime.wrap(function _callee8$(_context8) {
365
- while (1) switch (_context8.prev = _context8.next) {
416
+ return _regeneratorRuntime.wrap(function _callee9$(_context9) {
417
+ while (1) switch (_context9.prev = _context9.next) {
366
418
  case 0:
367
419
  mockShare.mockRejectedValueOnce(new Error('Share failed'));
368
- _setup8 = setup(), getByTestId = _setup8.getByTestId;
369
- _context8.next = 4;
420
+ _setup9 = setup(), getByTestId = _setup9.getByTestId;
421
+ _context9.next = 4;
370
422
  return waitFor(function () {
371
423
  expect(getByTestId('btn-send')).toBeTruthy();
372
424
  });
373
425
 
374
426
  case 4:
375
427
  fireEvent.click(getByTestId('btn-send'));
376
- _context8.next = 7;
428
+ _context9.next = 7;
377
429
  return waitFor(function () {
378
430
  expect(mockShowAlert).toHaveBeenCalledWith({
379
431
  message: 'Error while sharing folder',
@@ -385,20 +437,20 @@ describe('FederatedFolderModal', function () {
385
437
 
386
438
  case 7:
387
439
  case "end":
388
- return _context8.stop();
440
+ return _context9.stop();
389
441
  }
390
- }, _callee8);
442
+ }, _callee9);
391
443
  })));
392
444
  });
393
445
  describe('onSetType callback', function () {
394
- it('should move recipient from readOnlyRecipients to recipients when setting two-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee9() {
395
- var _setup9, getByTestId;
446
+ it('should move recipient from readOnlyRecipients to recipients when setting two-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10() {
447
+ var _setup10, getByTestId;
396
448
 
397
- return _regeneratorRuntime.wrap(function _callee9$(_context9) {
398
- while (1) switch (_context9.prev = _context9.next) {
449
+ return _regeneratorRuntime.wrap(function _callee10$(_context10) {
450
+ while (1) switch (_context10.prev = _context10.next) {
399
451
  case 0:
400
- _setup9 = setup(), getByTestId = _setup9.getByTestId;
401
- _context9.next = 3;
452
+ _setup10 = setup(), getByTestId = _setup10.getByTestId;
453
+ _context10.next = 3;
402
454
  return waitFor(function () {
403
455
  expect(getByTestId('btn-share')).toBeTruthy();
404
456
  });
@@ -406,7 +458,7 @@ describe('FederatedFolderModal', function () {
406
458
  case 3:
407
459
  fireEvent.click(getByTestId('btn-share')); // After btn-share: combined recipients = 2 (1 readWrite + 1 readOnly)
408
460
 
409
- _context9.next = 6;
461
+ _context10.next = 6;
410
462
  return waitFor(function () {
411
463
  expect(getByTestId('recipients-count').textContent).toBe('2');
412
464
  expect(getByTestId('readOnly-recipients-count').textContent).toBe('1');
@@ -415,7 +467,7 @@ describe('FederatedFolderModal', function () {
415
467
  case 6:
416
468
  fireEvent.click(getByTestId('btn-set-type-two-way')); // After setting two-way: Bob moves from readOnlyRecipients to recipients
417
469
 
418
- _context9.next = 9;
470
+ _context10.next = 9;
419
471
  return waitFor(function () {
420
472
  expect(getByTestId('recipients-count').textContent).toBe('2');
421
473
  expect(getByTestId('readOnly-recipients-count').textContent).toBe('0');
@@ -423,18 +475,18 @@ describe('FederatedFolderModal', function () {
423
475
 
424
476
  case 9:
425
477
  case "end":
426
- return _context9.stop();
478
+ return _context10.stop();
427
479
  }
428
- }, _callee9);
480
+ }, _callee10);
429
481
  })));
430
- it('should move recipient from recipients to readOnlyRecipients when setting one-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10() {
431
- var _setup10, getByTestId;
482
+ it('should move recipient from recipients to readOnlyRecipients when setting one-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee11() {
483
+ var _setup11, getByTestId;
432
484
 
433
- return _regeneratorRuntime.wrap(function _callee10$(_context10) {
434
- while (1) switch (_context10.prev = _context10.next) {
485
+ return _regeneratorRuntime.wrap(function _callee11$(_context11) {
486
+ while (1) switch (_context11.prev = _context11.next) {
435
487
  case 0:
436
- _setup10 = setup(), getByTestId = _setup10.getByTestId;
437
- _context10.next = 3;
488
+ _setup11 = setup(), getByTestId = _setup11.getByTestId;
489
+ _context11.next = 3;
438
490
  return waitFor(function () {
439
491
  expect(getByTestId('btn-share')).toBeTruthy();
440
492
  });
@@ -442,7 +494,7 @@ describe('FederatedFolderModal', function () {
442
494
  case 3:
443
495
  fireEvent.click(getByTestId('btn-share')); // After btn-share: combined recipients = 2 (1 readWrite + 1 readOnly)
444
496
 
445
- _context10.next = 6;
497
+ _context11.next = 6;
446
498
  return waitFor(function () {
447
499
  expect(getByTestId('recipients-count').textContent).toBe('2');
448
500
  expect(getByTestId('readOnly-recipients-count').textContent).toBe('1');
@@ -452,7 +504,7 @@ describe('FederatedFolderModal', function () {
452
504
  fireEvent.click(getByTestId('btn-set-type-one-way')); // After setting one-way: Alice moves from recipients to readOnlyRecipients
453
505
  // Combined count stays at 2, but readOnlyRecipients increases from 1 to 2
454
506
 
455
- _context10.next = 9;
507
+ _context11.next = 9;
456
508
  return waitFor(function () {
457
509
  expect(getByTestId('recipients-count').textContent).toBe('2');
458
510
  expect(getByTestId('readOnly-recipients-count').textContent).toBe('2');
@@ -460,54 +512,54 @@ describe('FederatedFolderModal', function () {
460
512
 
461
513
  case 9:
462
514
  case "end":
463
- return _context10.stop();
515
+ return _context11.stop();
464
516
  }
465
- }, _callee10);
517
+ }, _callee11);
466
518
  })));
467
519
  });
468
520
  describe('onRevoke callback', function () {
469
- it('should remove recipient from both lists', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee11() {
470
- var _setup11, getByTestId;
521
+ it('should remove recipient from both lists', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee12() {
522
+ var _setup12, getByTestId;
471
523
 
472
- return _regeneratorRuntime.wrap(function _callee11$(_context11) {
473
- while (1) switch (_context11.prev = _context11.next) {
524
+ return _regeneratorRuntime.wrap(function _callee12$(_context12) {
525
+ while (1) switch (_context12.prev = _context12.next) {
474
526
  case 0:
475
- _setup11 = setup(), getByTestId = _setup11.getByTestId;
476
- _context11.next = 3;
527
+ _setup12 = setup(), getByTestId = _setup12.getByTestId;
528
+ _context12.next = 3;
477
529
  return waitFor(function () {
478
530
  expect(getByTestId('btn-share')).toBeTruthy();
479
531
  });
480
532
 
481
533
  case 3:
482
534
  fireEvent.click(getByTestId('btn-share'));
483
- _context11.next = 6;
535
+ _context12.next = 6;
484
536
  return waitFor(function () {
485
537
  expect(getByTestId('recipients-count').textContent).toBe('2');
486
538
  });
487
539
 
488
540
  case 6:
489
541
  fireEvent.click(getByTestId('btn-revoke'));
490
- _context11.next = 9;
542
+ _context12.next = 9;
491
543
  return waitFor(function () {
492
544
  expect(getByTestId('recipients-count').textContent).toBe('1');
493
545
  });
494
546
 
495
547
  case 9:
496
548
  case "end":
497
- return _context11.stop();
549
+ return _context12.stop();
498
550
  }
499
- }, _callee11);
551
+ }, _callee12);
500
552
  })));
501
553
  });
502
554
  describe('onClose callback', function () {
503
- it('should call onClose when close button is clicked', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee12() {
504
- var _setup12, getByTestId;
555
+ it('should call onClose when close button is clicked', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee13() {
556
+ var _setup13, getByTestId;
505
557
 
506
- return _regeneratorRuntime.wrap(function _callee12$(_context12) {
507
- while (1) switch (_context12.prev = _context12.next) {
558
+ return _regeneratorRuntime.wrap(function _callee13$(_context13) {
559
+ while (1) switch (_context13.prev = _context13.next) {
508
560
  case 0:
509
- _setup12 = setup(), getByTestId = _setup12.getByTestId;
510
- _context12.next = 3;
561
+ _setup13 = setup(), getByTestId = _setup13.getByTestId;
562
+ _context13.next = 3;
511
563
  return waitFor(function () {
512
564
  expect(getByTestId('btn-close')).toBeTruthy();
513
565
  });
@@ -518,9 +570,9 @@ describe('FederatedFolderModal', function () {
518
570
 
519
571
  case 5:
520
572
  case "end":
521
- return _context12.stop();
573
+ return _context13.stop();
522
574
  }
523
- }, _callee12);
575
+ }, _callee13);
524
576
  })));
525
577
  });
526
578
  describe('existing recipients', function () {
@@ -541,54 +593,54 @@ describe('FederatedFolderModal', function () {
541
593
  sharingId: 'abc',
542
594
  memberIndex: 1
543
595
  }];
544
- it('should display existing recipients from getRecipients', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee13() {
545
- var _setup13, getByTestId;
596
+ it('should display existing recipients from getRecipients', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee14() {
597
+ var _setup14, getByTestId;
546
598
 
547
- return _regeneratorRuntime.wrap(function _callee13$(_context13) {
548
- while (1) switch (_context13.prev = _context13.next) {
599
+ return _regeneratorRuntime.wrap(function _callee14$(_context14) {
600
+ while (1) switch (_context14.prev = _context14.next) {
549
601
  case 0:
550
602
  mockGetRecipients.mockReturnValue(existingRecipients);
551
- _setup13 = setup(), getByTestId = _setup13.getByTestId;
552
- _context13.next = 4;
603
+ _setup14 = setup(), getByTestId = _setup14.getByTestId;
604
+ _context14.next = 4;
553
605
  return waitFor(function () {
554
606
  expect(getByTestId('recipients-count').textContent).toBe('2');
555
607
  });
556
608
 
557
609
  case 4:
558
610
  case "end":
559
- return _context13.stop();
611
+ return _context14.stop();
560
612
  }
561
- }, _callee13);
613
+ }, _callee14);
562
614
  })));
563
- it('should pass existing recipients as currentRecipients', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee14() {
564
- var _setup14, getByTestId;
615
+ it('should pass existing recipients as currentRecipients', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee15() {
616
+ var _setup15, getByTestId;
565
617
 
566
- return _regeneratorRuntime.wrap(function _callee14$(_context14) {
567
- while (1) switch (_context14.prev = _context14.next) {
618
+ return _regeneratorRuntime.wrap(function _callee15$(_context15) {
619
+ while (1) switch (_context15.prev = _context15.next) {
568
620
  case 0:
569
621
  mockGetRecipients.mockReturnValue(existingRecipients);
570
- _setup14 = setup(), getByTestId = _setup14.getByTestId;
571
- _context14.next = 4;
622
+ _setup15 = setup(), getByTestId = _setup15.getByTestId;
623
+ _context15.next = 4;
572
624
  return waitFor(function () {
573
625
  expect(getByTestId('recipients-count').textContent).toBe('2');
574
626
  });
575
627
 
576
628
  case 4:
577
629
  case "end":
578
- return _context14.stop();
630
+ return _context15.stop();
579
631
  }
580
- }, _callee14);
632
+ }, _callee15);
581
633
  })));
582
- it('should call revoke for existing member', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee15() {
583
- var _setup15, getByTestId;
634
+ it('should call revoke for existing member', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee16() {
635
+ var _setup16, getByTestId;
584
636
 
585
- return _regeneratorRuntime.wrap(function _callee15$(_context15) {
586
- while (1) switch (_context15.prev = _context15.next) {
637
+ return _regeneratorRuntime.wrap(function _callee16$(_context16) {
638
+ while (1) switch (_context16.prev = _context16.next) {
587
639
  case 0:
588
640
  mockGetRecipients.mockReturnValue(existingRecipients);
589
641
  mockRevoke.mockResolvedValueOnce({});
590
- _setup15 = setup(), getByTestId = _setup15.getByTestId;
591
- _context15.next = 5;
642
+ _setup16 = setup(), getByTestId = _setup16.getByTestId;
643
+ _context16.next = 5;
592
644
  return waitFor(function () {
593
645
  expect(getByTestId('dumb-modal')).toBeTruthy();
594
646
  });
@@ -596,7 +648,7 @@ describe('FederatedFolderModal', function () {
596
648
  case 5:
597
649
  // Simulate revoke of an existing member (non-virtual-shared-drive index)
598
650
  fireEvent.click(getByTestId('btn-revoke-existing'));
599
- _context15.next = 8;
651
+ _context16.next = 8;
600
652
  return waitFor(function () {
601
653
  expect(mockRevoke).toHaveBeenCalledWith({
602
654
  _id: 'folder-123'
@@ -605,27 +657,27 @@ describe('FederatedFolderModal', function () {
605
657
 
606
658
  case 8:
607
659
  case "end":
608
- return _context15.stop();
660
+ return _context16.stop();
609
661
  }
610
- }, _callee15);
662
+ }, _callee16);
611
663
  })));
612
664
  });
613
665
  describe('createContact callback', function () {
614
- it('should call client.create when creating a contact', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee16() {
615
- var _setup16, getByTestId;
666
+ it('should call client.create when creating a contact', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee17() {
667
+ var _setup17, getByTestId;
616
668
 
617
- return _regeneratorRuntime.wrap(function _callee16$(_context16) {
618
- while (1) switch (_context16.prev = _context16.next) {
669
+ return _regeneratorRuntime.wrap(function _callee17$(_context17) {
670
+ while (1) switch (_context17.prev = _context17.next) {
619
671
  case 0:
620
- _setup16 = setup(), getByTestId = _setup16.getByTestId;
621
- _context16.next = 3;
672
+ _setup17 = setup(), getByTestId = _setup17.getByTestId;
673
+ _context17.next = 3;
622
674
  return waitFor(function () {
623
675
  expect(getByTestId('btn-create-contact')).toBeTruthy();
624
676
  });
625
677
 
626
678
  case 3:
627
679
  fireEvent.click(getByTestId('btn-create-contact'));
628
- _context16.next = 6;
680
+ _context17.next = 6;
629
681
  return waitFor(function () {
630
682
  expect(client.create).toHaveBeenCalledWith('io.cozy.contacts', {
631
683
  email: 'test@example.com'
@@ -634,9 +686,9 @@ describe('FederatedFolderModal', function () {
634
686
 
635
687
  case 6:
636
688
  case "end":
637
- return _context16.stop();
689
+ return _context17.stop();
638
690
  }
639
- }, _callee16);
691
+ }, _callee17);
640
692
  })));
641
693
  });
642
694
  });
@@ -1,18 +1,41 @@
1
1
  import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
3
+
4
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
5
+
6
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
7
+
2
8
  import { models } from 'cozy-client';
3
9
  var ContactModel = models.contact;
4
10
  export var RECIPIENT_INDEX_PREFIX = 'virtual-shared-drive-sharing-';
5
11
  export var mergeAndDeduplicateRecipients = function mergeAndDeduplicateRecipients(arrays) {
6
- var combinedArray = arrays.flat();
12
+ var combinedArray = arrays.flat().map(function (item) {
13
+ return _objectSpread(_objectSpread({}, item), {}, {
14
+ id: item.id || item._id
15
+ });
16
+ });
7
17
  var seenIds = new Set();
8
- var uniqueArray = combinedArray.filter(function (item) {
9
- if (!seenIds.has(item.id)) {
10
- seenIds.add(item.id);
11
- return true;
18
+ var uniqueArray = combinedArray.reduce(function (acc, item) {
19
+ var id = item.id || item._id;
20
+
21
+ if (id == null) {
22
+ // Recipients without any identifier are kept as-is to avoid silent data loss
23
+ acc.push(item);
24
+ return acc;
12
25
  }
13
26
 
14
- return false;
15
- });
27
+ if (!seenIds.has(id)) {
28
+ seenIds.add(id); // Ensure both id and _id exist for downstream compatibility
29
+
30
+ var normalizedItem = !item.id || !item._id ? _objectSpread(_objectSpread({}, item), {}, {
31
+ id: item.id || item._id,
32
+ _id: item._id || item.id
33
+ }) : item;
34
+ acc.push(normalizedItem);
35
+ }
36
+
37
+ return acc;
38
+ }, []);
16
39
  return uniqueArray;
17
40
  };
18
41
  export var moveRecipientToReadWrite = function moveRecipientToReadWrite(federatedRecipients, _id) {
@@ -0,0 +1,208 @@
1
+ import { mergeAndDeduplicateRecipients, moveRecipientToReadWrite, moveRecipientToReadOnly, formatRecipients, RECIPIENT_INDEX_PREFIX } from './helpers';
2
+ describe('mergeAndDeduplicateRecipients', function () {
3
+ it('should merge multiple arrays into one', function () {
4
+ var result = mergeAndDeduplicateRecipients([[{
5
+ id: '1',
6
+ name: 'Alice'
7
+ }], [{
8
+ id: '2',
9
+ name: 'Bob'
10
+ }]]);
11
+ expect(result).toEqual([{
12
+ id: '1',
13
+ _id: '1',
14
+ name: 'Alice'
15
+ }, {
16
+ id: '2',
17
+ _id: '2',
18
+ name: 'Bob'
19
+ }]);
20
+ });
21
+ it('should deduplicate recipients with the same id', function () {
22
+ var result = mergeAndDeduplicateRecipients([[{
23
+ id: '1',
24
+ name: 'Alice'
25
+ }], [{
26
+ id: '1',
27
+ name: 'Alice'
28
+ }]]);
29
+ expect(result).toEqual([{
30
+ id: '1',
31
+ _id: '1',
32
+ name: 'Alice'
33
+ }]);
34
+ });
35
+ it('should normalize _id to id for deduplication', function () {
36
+ var result = mergeAndDeduplicateRecipients([[{
37
+ _id: '1',
38
+ name: 'Alice'
39
+ }], [{
40
+ _id: '2',
41
+ name: 'Bob'
42
+ }]]);
43
+ expect(result).toEqual([{
44
+ _id: '1',
45
+ id: '1',
46
+ name: 'Alice'
47
+ }, {
48
+ _id: '2',
49
+ id: '2',
50
+ name: 'Bob'
51
+ }]);
52
+ });
53
+ it('should deduplicate recipients when one has id and the other has _id', function () {
54
+ var result = mergeAndDeduplicateRecipients([[{
55
+ id: '1',
56
+ name: 'Alice'
57
+ }], [{
58
+ _id: '1',
59
+ name: 'Alice'
60
+ }]]);
61
+ expect(result).toEqual([{
62
+ id: '1',
63
+ _id: '1',
64
+ name: 'Alice'
65
+ }]);
66
+ });
67
+ it('should prefer existing id over _id when both are present', function () {
68
+ var result = mergeAndDeduplicateRecipients([[{
69
+ id: 'real-id',
70
+ _id: 'other-id',
71
+ name: 'Alice'
72
+ }]]);
73
+ expect(result).toEqual([{
74
+ id: 'real-id',
75
+ _id: 'other-id',
76
+ name: 'Alice'
77
+ }]);
78
+ });
79
+ it('should handle empty arrays', function () {
80
+ var result = mergeAndDeduplicateRecipients([[], []]);
81
+ expect(result).toEqual([]);
82
+ });
83
+ });
84
+ describe('moveRecipientToReadWrite', function () {
85
+ it('should move a recipient from readOnly to readWrite', function () {
86
+ var state = {
87
+ recipients: [{
88
+ _id: '1',
89
+ name: 'Alice'
90
+ }],
91
+ readOnlyRecipients: [{
92
+ _id: '2',
93
+ name: 'Bob'
94
+ }]
95
+ };
96
+ var result = moveRecipientToReadWrite(state, '2');
97
+ expect(result.recipients).toEqual([{
98
+ _id: '1',
99
+ name: 'Alice'
100
+ }, {
101
+ _id: '2',
102
+ name: 'Bob'
103
+ }]);
104
+ expect(result.readOnlyRecipients).toEqual([]);
105
+ });
106
+ it('should return unchanged state if recipient not found', function () {
107
+ var state = {
108
+ recipients: [{
109
+ _id: '1',
110
+ name: 'Alice'
111
+ }],
112
+ readOnlyRecipients: []
113
+ };
114
+ var result = moveRecipientToReadWrite(state, '999');
115
+ expect(result).toBe(state);
116
+ });
117
+ });
118
+ describe('moveRecipientToReadOnly', function () {
119
+ it('should move a recipient from readWrite to readOnly', function () {
120
+ var state = {
121
+ recipients: [{
122
+ _id: '1',
123
+ name: 'Alice'
124
+ }],
125
+ readOnlyRecipients: [{
126
+ _id: '2',
127
+ name: 'Bob'
128
+ }]
129
+ };
130
+ var result = moveRecipientToReadOnly(state, '1');
131
+ expect(result.recipients).toEqual([]);
132
+ expect(result.readOnlyRecipients).toEqual([{
133
+ _id: '2',
134
+ name: 'Bob'
135
+ }, {
136
+ _id: '1',
137
+ name: 'Alice'
138
+ }]);
139
+ });
140
+ it('should return unchanged state if recipient not found', function () {
141
+ var state = {
142
+ recipients: [],
143
+ readOnlyRecipients: [{
144
+ _id: '1',
145
+ name: 'Alice'
146
+ }]
147
+ };
148
+ var result = moveRecipientToReadOnly(state, '999');
149
+ expect(result).toBe(state);
150
+ });
151
+ });
152
+ describe('formatRecipients', function () {
153
+ it('should format readWrite and readOnly recipients with correct types', function () {
154
+ var input = {
155
+ recipients: [{
156
+ _id: '1',
157
+ displayName: 'Alice',
158
+ email: [{
159
+ address: 'alice@example.com',
160
+ primary: true
161
+ }]
162
+ }],
163
+ readOnlyRecipients: [{
164
+ _id: '2',
165
+ displayName: 'Bob',
166
+ email: [{
167
+ address: 'bob@example.com',
168
+ primary: true
169
+ }]
170
+ }]
171
+ };
172
+ var result = formatRecipients(input);
173
+ expect(result).toHaveLength(2);
174
+ expect(result[0]).toMatchObject({
175
+ index: "".concat(RECIPIENT_INDEX_PREFIX, "1"),
176
+ type: 'two-way',
177
+ public_name: 'Alice'
178
+ });
179
+ expect(result[1]).toMatchObject({
180
+ index: "".concat(RECIPIENT_INDEX_PREFIX, "2"),
181
+ type: 'one-way',
182
+ public_name: 'Bob'
183
+ });
184
+ });
185
+ it('should sort recipients by public_name', function () {
186
+ var input = {
187
+ recipients: [{
188
+ _id: '1',
189
+ displayName: 'Zara',
190
+ email: [{
191
+ address: 'zara@example.com',
192
+ primary: true
193
+ }]
194
+ }, {
195
+ _id: '2',
196
+ displayName: 'Alice',
197
+ email: [{
198
+ address: 'alice@example.com',
199
+ primary: true
200
+ }]
201
+ }],
202
+ readOnlyRecipients: []
203
+ };
204
+ var result = formatRecipients(input);
205
+ expect(result[0].public_name).toBe('Alice');
206
+ expect(result[1].public_name).toBe('Zara');
207
+ });
208
+ });
@@ -51,6 +51,7 @@
51
51
  border 1px solid var(--primaryColor)
52
52
 
53
53
  input[type=email]
54
+ color var(--primaryTextColor)
54
55
  width auto
55
56
  flex-grow 1
56
57
  padding .5rem 0
@@ -426,6 +426,7 @@ input[type=text] {
426
426
  border: 1px solid var(--primaryColor);
427
427
  }
428
428
  .autosuggest__recipientsContainer___1JYxA input[type=email] {
429
+ color: var(--primaryTextColor);
429
430
  width: auto;
430
431
  flex-grow: 1;
431
432
  padding: 0.5rem 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-sharing",
3
- "version": "28.11.1",
3
+ "version": "28.11.3",
4
4
  "description": "Provides sharing login for React applications.",
5
5
  "main": "dist/index.js",
6
6
  "author": "Cozy",
@@ -71,6 +71,7 @@
71
71
  "peerDependencies": {
72
72
  "cozy-client": ">=60.22.0",
73
73
  "cozy-device-helper": ">=3.7.1",
74
+ "cozy-flags": ">=4.8.1",
74
75
  "cozy-intent": ">=2.29.1",
75
76
  "cozy-minilog": ">=3.9.1",
76
77
  "cozy-ui": ">=135.0.0",
@@ -83,5 +84,5 @@
83
84
  "sideEffects": [
84
85
  "*.css"
85
86
  ],
86
- "gitHead": "e726cac12603dffee9a88debf8618c47bc628082"
87
+ "gitHead": "e968ecf2d667b49629b281327c896971a3706105"
87
88
  }
@@ -10,6 +10,7 @@ import withLocales from '../../hoc/withLocales'
10
10
  import { useSharingContext } from '../../hooks/useSharingContext'
11
11
  import {
12
12
  formatRecipients,
13
+ mergeAndDeduplicateRecipients,
13
14
  moveRecipientToReadWrite,
14
15
  moveRecipientToReadOnly,
15
16
  RECIPIENT_INDEX_PREFIX
@@ -66,10 +67,16 @@ export const FederatedFolderModal = withLocales(
66
67
  const [folderName] = useState(existingDocument?.name || '')
67
68
 
68
69
  const onShare = params => {
69
- setFederatedRecipients({
70
- recipients: params.recipients || [],
71
- readOnlyRecipients: params.readOnlyRecipients || []
72
- })
70
+ setFederatedRecipients(prev => ({
71
+ recipients: mergeAndDeduplicateRecipients([
72
+ prev.recipients,
73
+ params.recipients || []
74
+ ]),
75
+ readOnlyRecipients: mergeAndDeduplicateRecipients([
76
+ prev.readOnlyRecipients,
77
+ params.readOnlyRecipients || []
78
+ ])
79
+ }))
73
80
  }
74
81
 
75
82
  const onSend = async () => {
@@ -55,13 +55,27 @@ jest.mock('./DumbFederatedFolderModal', () => ({
55
55
  data-testid="btn-share"
56
56
  onClick={() =>
57
57
  onShare({
58
- recipients: [{ _id: 'r1', displayName: 'Alice' }],
59
- readOnlyRecipients: [{ _id: 'r2', displayName: 'Bob' }]
58
+ recipients: [{ _id: 'r1', id: 'r1', displayName: 'Alice' }],
59
+ readOnlyRecipients: [{ _id: 'r2', id: 'r2', displayName: 'Bob' }]
60
60
  })
61
61
  }
62
62
  >
63
63
  Share
64
64
  </button>
65
+ <button
66
+ data-testid="btn-share-other"
67
+ onClick={() =>
68
+ onShare({
69
+ recipients: [
70
+ { _id: 'r1', id: 'r1', displayName: 'Alice' },
71
+ { _id: 'r3', id: 'r3', displayName: 'Charlie' }
72
+ ],
73
+ readOnlyRecipients: []
74
+ })
75
+ }
76
+ >
77
+ Share Other
78
+ </button>
65
79
  <button data-testid="btn-send" onClick={onSend}>
66
80
  Send
67
81
  </button>
@@ -213,6 +227,26 @@ describe('FederatedFolderModal', () => {
213
227
  expect(getByTestId('recipients-count').textContent).toBe('2')
214
228
  })
215
229
  })
230
+
231
+ it('should accumulate recipients when onShare is called multiple times', async () => {
232
+ const { getByTestId } = setup()
233
+
234
+ await waitFor(() => {
235
+ expect(getByTestId('recipients-count').textContent).toBe('0')
236
+ })
237
+
238
+ fireEvent.click(getByTestId('btn-share'))
239
+
240
+ await waitFor(() => {
241
+ expect(getByTestId('recipients-count').textContent).toBe('2')
242
+ })
243
+
244
+ fireEvent.click(getByTestId('btn-share-other'))
245
+
246
+ await waitFor(() => {
247
+ expect(getByTestId('recipients-count').textContent).toBe('3')
248
+ })
249
+ })
216
250
  })
217
251
 
218
252
  describe('onSend callback', () => {
@@ -231,8 +265,8 @@ describe('FederatedFolderModal', () => {
231
265
  expect(mockShare).toHaveBeenCalledWith({
232
266
  description: 'My Test Folder',
233
267
  document: mockDocument,
234
- recipients: [{ _id: 'r1', displayName: 'Alice' }],
235
- readOnlyRecipients: [{ _id: 'r2', displayName: 'Bob' }],
268
+ recipients: [{ _id: 'r1', id: 'r1', displayName: 'Alice' }],
269
+ readOnlyRecipients: [{ _id: 'r2', id: 'r2', displayName: 'Bob' }],
236
270
  sharedDrive: true,
237
271
  openSharing: false
238
272
  })
@@ -5,17 +5,34 @@ const ContactModel = models.contact
5
5
  export const RECIPIENT_INDEX_PREFIX = 'virtual-shared-drive-sharing-'
6
6
 
7
7
  export const mergeAndDeduplicateRecipients = arrays => {
8
- const combinedArray = arrays.flat()
8
+ const combinedArray = arrays.flat().map(item => ({
9
+ ...item,
10
+ id: item.id || item._id
11
+ }))
9
12
 
10
13
  const seenIds = new Set()
11
14
 
12
- const uniqueArray = combinedArray.filter(item => {
13
- if (!seenIds.has(item.id)) {
14
- seenIds.add(item.id)
15
- return true
15
+ const uniqueArray = combinedArray.reduce((acc, item) => {
16
+ const id = item.id || item._id
17
+
18
+ if (id == null) {
19
+ // Recipients without any identifier are kept as-is to avoid silent data loss
20
+ acc.push(item)
21
+ return acc
16
22
  }
17
- return false
18
- })
23
+
24
+ if (!seenIds.has(id)) {
25
+ seenIds.add(id)
26
+ // Ensure both id and _id exist for downstream compatibility
27
+ const normalizedItem =
28
+ !item.id || !item._id
29
+ ? { ...item, id: item.id || item._id, _id: item._id || item.id }
30
+ : item
31
+ acc.push(normalizedItem)
32
+ }
33
+
34
+ return acc
35
+ }, [])
19
36
 
20
37
  return uniqueArray
21
38
  }
@@ -0,0 +1,161 @@
1
+ import {
2
+ mergeAndDeduplicateRecipients,
3
+ moveRecipientToReadWrite,
4
+ moveRecipientToReadOnly,
5
+ formatRecipients,
6
+ RECIPIENT_INDEX_PREFIX
7
+ } from './helpers'
8
+
9
+ describe('mergeAndDeduplicateRecipients', () => {
10
+ it('should merge multiple arrays into one', () => {
11
+ const result = mergeAndDeduplicateRecipients([
12
+ [{ id: '1', name: 'Alice' }],
13
+ [{ id: '2', name: 'Bob' }]
14
+ ])
15
+ expect(result).toEqual([
16
+ { id: '1', _id: '1', name: 'Alice' },
17
+ { id: '2', _id: '2', name: 'Bob' }
18
+ ])
19
+ })
20
+
21
+ it('should deduplicate recipients with the same id', () => {
22
+ const result = mergeAndDeduplicateRecipients([
23
+ [{ id: '1', name: 'Alice' }],
24
+ [{ id: '1', name: 'Alice' }]
25
+ ])
26
+ expect(result).toEqual([{ id: '1', _id: '1', name: 'Alice' }])
27
+ })
28
+
29
+ it('should normalize _id to id for deduplication', () => {
30
+ const result = mergeAndDeduplicateRecipients([
31
+ [{ _id: '1', name: 'Alice' }],
32
+ [{ _id: '2', name: 'Bob' }]
33
+ ])
34
+ expect(result).toEqual([
35
+ { _id: '1', id: '1', name: 'Alice' },
36
+ { _id: '2', id: '2', name: 'Bob' }
37
+ ])
38
+ })
39
+
40
+ it('should deduplicate recipients when one has id and the other has _id', () => {
41
+ const result = mergeAndDeduplicateRecipients([
42
+ [{ id: '1', name: 'Alice' }],
43
+ [{ _id: '1', name: 'Alice' }]
44
+ ])
45
+ expect(result).toEqual([{ id: '1', _id: '1', name: 'Alice' }])
46
+ })
47
+
48
+ it('should prefer existing id over _id when both are present', () => {
49
+ const result = mergeAndDeduplicateRecipients([
50
+ [{ id: 'real-id', _id: 'other-id', name: 'Alice' }]
51
+ ])
52
+ expect(result).toEqual([{ id: 'real-id', _id: 'other-id', name: 'Alice' }])
53
+ })
54
+
55
+ it('should handle empty arrays', () => {
56
+ const result = mergeAndDeduplicateRecipients([[], []])
57
+ expect(result).toEqual([])
58
+ })
59
+ })
60
+
61
+ describe('moveRecipientToReadWrite', () => {
62
+ it('should move a recipient from readOnly to readWrite', () => {
63
+ const state = {
64
+ recipients: [{ _id: '1', name: 'Alice' }],
65
+ readOnlyRecipients: [{ _id: '2', name: 'Bob' }]
66
+ }
67
+ const result = moveRecipientToReadWrite(state, '2')
68
+ expect(result.recipients).toEqual([
69
+ { _id: '1', name: 'Alice' },
70
+ { _id: '2', name: 'Bob' }
71
+ ])
72
+ expect(result.readOnlyRecipients).toEqual([])
73
+ })
74
+
75
+ it('should return unchanged state if recipient not found', () => {
76
+ const state = {
77
+ recipients: [{ _id: '1', name: 'Alice' }],
78
+ readOnlyRecipients: []
79
+ }
80
+ const result = moveRecipientToReadWrite(state, '999')
81
+ expect(result).toBe(state)
82
+ })
83
+ })
84
+
85
+ describe('moveRecipientToReadOnly', () => {
86
+ it('should move a recipient from readWrite to readOnly', () => {
87
+ const state = {
88
+ recipients: [{ _id: '1', name: 'Alice' }],
89
+ readOnlyRecipients: [{ _id: '2', name: 'Bob' }]
90
+ }
91
+ const result = moveRecipientToReadOnly(state, '1')
92
+ expect(result.recipients).toEqual([])
93
+ expect(result.readOnlyRecipients).toEqual([
94
+ { _id: '2', name: 'Bob' },
95
+ { _id: '1', name: 'Alice' }
96
+ ])
97
+ })
98
+
99
+ it('should return unchanged state if recipient not found', () => {
100
+ const state = {
101
+ recipients: [],
102
+ readOnlyRecipients: [{ _id: '1', name: 'Alice' }]
103
+ }
104
+ const result = moveRecipientToReadOnly(state, '999')
105
+ expect(result).toBe(state)
106
+ })
107
+ })
108
+
109
+ describe('formatRecipients', () => {
110
+ it('should format readWrite and readOnly recipients with correct types', () => {
111
+ const input = {
112
+ recipients: [
113
+ {
114
+ _id: '1',
115
+ displayName: 'Alice',
116
+ email: [{ address: 'alice@example.com', primary: true }]
117
+ }
118
+ ],
119
+ readOnlyRecipients: [
120
+ {
121
+ _id: '2',
122
+ displayName: 'Bob',
123
+ email: [{ address: 'bob@example.com', primary: true }]
124
+ }
125
+ ]
126
+ }
127
+ const result = formatRecipients(input)
128
+ expect(result).toHaveLength(2)
129
+ expect(result[0]).toMatchObject({
130
+ index: `${RECIPIENT_INDEX_PREFIX}1`,
131
+ type: 'two-way',
132
+ public_name: 'Alice'
133
+ })
134
+ expect(result[1]).toMatchObject({
135
+ index: `${RECIPIENT_INDEX_PREFIX}2`,
136
+ type: 'one-way',
137
+ public_name: 'Bob'
138
+ })
139
+ })
140
+
141
+ it('should sort recipients by public_name', () => {
142
+ const input = {
143
+ recipients: [
144
+ {
145
+ _id: '1',
146
+ displayName: 'Zara',
147
+ email: [{ address: 'zara@example.com', primary: true }]
148
+ },
149
+ {
150
+ _id: '2',
151
+ displayName: 'Alice',
152
+ email: [{ address: 'alice@example.com', primary: true }]
153
+ }
154
+ ],
155
+ readOnlyRecipients: []
156
+ }
157
+ const result = formatRecipients(input)
158
+ expect(result[0].public_name).toBe('Alice')
159
+ expect(result[1].public_name).toBe('Zara')
160
+ })
161
+ })
@@ -51,6 +51,7 @@
51
51
  border 1px solid var(--primaryColor)
52
52
 
53
53
  input[type=email]
54
+ color var(--primaryTextColor)
54
55
  width auto
55
56
  flex-grow 1
56
57
  padding .5rem 0