cozy-sharing 28.11.2 → 29.0.0
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 +18 -0
- package/dist/components/FederatedFolder/FederatedFolderModal.js +6 -4
- package/dist/components/FederatedFolder/FederatedFolderModal.spec.js +150 -98
- package/dist/components/SharedDrive/helpers.js +30 -7
- package/dist/components/SharedDrive/helpers.spec.js +208 -0
- package/dist/components/__snapshots__/SharedStatus.spec.js.snap +3 -3
- package/package.json +4 -3
- package/src/components/FederatedFolder/FederatedFolderModal.jsx +11 -4
- package/src/components/FederatedFolder/FederatedFolderModal.spec.jsx +38 -4
- package/src/components/SharedDrive/helpers.js +24 -7
- package/src/components/SharedDrive/helpers.spec.js +161 -0
- package/src/components/__snapshots__/SharedStatus.spec.js.snap +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,24 @@
|
|
|
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
|
+
# [29.0.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@28.11.3...cozy-sharing@29.0.0) (2026-03-24)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
- **cozy-ui-plus:** Upgrade cozy-ui ([b01d192](https://github.com/cozy/cozy-libs/commit/b01d1926f00a81ff2eb2c0b03378ea06fefd5fcc))
|
|
11
|
+
|
|
12
|
+
### BREAKING CHANGES
|
|
13
|
+
|
|
14
|
+
- **cozy-ui-plus:** You must have `cozy-ui >= 138.1.0`
|
|
15
|
+
|
|
16
|
+
## [28.11.3](https://github.com/cozy/cozy-libs/compare/cozy-sharing@28.11.2...cozy-sharing@28.11.3) (2026-03-23)
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
- **cozy-sharing:** Accumulate recipients in federated folder sharing ([a626ee4](https://github.com/cozy/cozy-libs/commit/a626ee4793486d1dcdb5e419a59f39caee6f09f7))
|
|
21
|
+
- **cozy-sharing:** Normalize recipient id in dedup helper ([6bd9292](https://github.com/cozy/cozy-libs/commit/6bd9292ef50f01307f6bfb335812dfc4ecce5c25))
|
|
22
|
+
- Remove warning on cozy-flags missing dependency ([25f9864](https://github.com/cozy/cozy-libs/commit/25f98641a5f9c1f22fa759abe7382d422d4567ef))
|
|
23
|
+
|
|
6
24
|
## [28.11.2](https://github.com/cozy/cozy-libs/compare/cozy-sharing@28.11.1...cozy-sharing@28.11.2) (2026-03-18)
|
|
7
25
|
|
|
8
26
|
### Bug Fixes
|
|
@@ -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
|
-
|
|
103
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
378
|
+
return _context7.stop();
|
|
327
379
|
}
|
|
328
|
-
},
|
|
380
|
+
}, _callee7);
|
|
329
381
|
})));
|
|
330
|
-
it('should show success alert and close modal on successful share', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function
|
|
331
|
-
var
|
|
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
|
|
334
|
-
while (1) switch (
|
|
385
|
+
return _regeneratorRuntime.wrap(function _callee8$(_context8) {
|
|
386
|
+
while (1) switch (_context8.prev = _context8.next) {
|
|
335
387
|
case 0:
|
|
336
388
|
mockShare.mockResolvedValueOnce({});
|
|
337
|
-
|
|
338
|
-
|
|
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
|
-
|
|
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
|
|
409
|
+
return _context8.stop();
|
|
358
410
|
}
|
|
359
|
-
},
|
|
411
|
+
}, _callee8);
|
|
360
412
|
})));
|
|
361
|
-
it('should show error alert when share fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function
|
|
362
|
-
var
|
|
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
|
|
365
|
-
while (1) switch (
|
|
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
|
-
|
|
369
|
-
|
|
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
|
-
|
|
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
|
|
440
|
+
return _context9.stop();
|
|
389
441
|
}
|
|
390
|
-
},
|
|
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
|
|
395
|
-
var
|
|
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
|
|
398
|
-
while (1) switch (
|
|
449
|
+
return _regeneratorRuntime.wrap(function _callee10$(_context10) {
|
|
450
|
+
while (1) switch (_context10.prev = _context10.next) {
|
|
399
451
|
case 0:
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
478
|
+
return _context10.stop();
|
|
427
479
|
}
|
|
428
|
-
},
|
|
480
|
+
}, _callee10);
|
|
429
481
|
})));
|
|
430
|
-
it('should move recipient from recipients to readOnlyRecipients when setting one-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function
|
|
431
|
-
var
|
|
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
|
|
434
|
-
while (1) switch (
|
|
485
|
+
return _regeneratorRuntime.wrap(function _callee11$(_context11) {
|
|
486
|
+
while (1) switch (_context11.prev = _context11.next) {
|
|
435
487
|
case 0:
|
|
436
|
-
|
|
437
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
515
|
+
return _context11.stop();
|
|
464
516
|
}
|
|
465
|
-
},
|
|
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
|
|
470
|
-
var
|
|
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
|
|
473
|
-
while (1) switch (
|
|
524
|
+
return _regeneratorRuntime.wrap(function _callee12$(_context12) {
|
|
525
|
+
while (1) switch (_context12.prev = _context12.next) {
|
|
474
526
|
case 0:
|
|
475
|
-
|
|
476
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
549
|
+
return _context12.stop();
|
|
498
550
|
}
|
|
499
|
-
},
|
|
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
|
|
504
|
-
var
|
|
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
|
|
507
|
-
while (1) switch (
|
|
558
|
+
return _regeneratorRuntime.wrap(function _callee13$(_context13) {
|
|
559
|
+
while (1) switch (_context13.prev = _context13.next) {
|
|
508
560
|
case 0:
|
|
509
|
-
|
|
510
|
-
|
|
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
|
|
573
|
+
return _context13.stop();
|
|
522
574
|
}
|
|
523
|
-
},
|
|
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
|
|
545
|
-
var
|
|
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
|
|
548
|
-
while (1) switch (
|
|
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
|
-
|
|
552
|
-
|
|
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
|
|
611
|
+
return _context14.stop();
|
|
560
612
|
}
|
|
561
|
-
},
|
|
613
|
+
}, _callee14);
|
|
562
614
|
})));
|
|
563
|
-
it('should pass existing recipients as currentRecipients', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function
|
|
564
|
-
var
|
|
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
|
|
567
|
-
while (1) switch (
|
|
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
|
-
|
|
571
|
-
|
|
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
|
|
630
|
+
return _context15.stop();
|
|
579
631
|
}
|
|
580
|
-
},
|
|
632
|
+
}, _callee15);
|
|
581
633
|
})));
|
|
582
|
-
it('should call revoke for existing member', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function
|
|
583
|
-
var
|
|
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
|
|
586
|
-
while (1) switch (
|
|
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
|
-
|
|
591
|
-
|
|
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
|
-
|
|
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
|
|
660
|
+
return _context16.stop();
|
|
609
661
|
}
|
|
610
|
-
},
|
|
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
|
|
615
|
-
var
|
|
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
|
|
618
|
-
while (1) switch (
|
|
669
|
+
return _regeneratorRuntime.wrap(function _callee17$(_context17) {
|
|
670
|
+
while (1) switch (_context17.prev = _context17.next) {
|
|
619
671
|
case 0:
|
|
620
|
-
|
|
621
|
-
|
|
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
|
-
|
|
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
|
|
689
|
+
return _context17.stop();
|
|
638
690
|
}
|
|
639
|
-
},
|
|
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.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
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
|
+
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
exports[`SharedStatus component should display the link if there is a link 1`] = `
|
|
4
4
|
<div>
|
|
5
5
|
<div
|
|
6
|
-
class="TwakeTheme--light
|
|
6
|
+
class="TwakeTheme--light u-dc"
|
|
7
7
|
>
|
|
8
8
|
<span
|
|
9
9
|
class="shared-status"
|
|
@@ -47,7 +47,7 @@ exports[`SharedStatus component should display the link if there is a link 1`] =
|
|
|
47
47
|
exports[`SharedStatus component should have the right display if there is several recipients 1`] = `
|
|
48
48
|
<div>
|
|
49
49
|
<div
|
|
50
|
-
class="TwakeTheme--light
|
|
50
|
+
class="TwakeTheme--light u-dc"
|
|
51
51
|
>
|
|
52
52
|
<span
|
|
53
53
|
class="shared-status"
|
|
@@ -84,7 +84,7 @@ exports[`SharedStatus component should have the right display if there is severa
|
|
|
84
84
|
exports[`SharedStatus component should just render a span if no sharing 1`] = `
|
|
85
85
|
<div>
|
|
86
86
|
<div
|
|
87
|
-
class="TwakeTheme--light
|
|
87
|
+
class="TwakeTheme--light u-dc"
|
|
88
88
|
>
|
|
89
89
|
<span
|
|
90
90
|
class="shared-status"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cozy-sharing",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "29.0.0",
|
|
4
4
|
"description": "Provides sharing login for React applications.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"author": "Cozy",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"cozy-intent": "^2.31.1",
|
|
60
60
|
"cozy-minilog": "^3.10.1",
|
|
61
61
|
"cozy-ui": "^135.0.0",
|
|
62
|
-
"cozy-ui-plus": "^
|
|
62
|
+
"cozy-ui-plus": "^6.0.0",
|
|
63
63
|
"jest": "30.3.0",
|
|
64
64
|
"jest-environment-jsdom": "30.3.0",
|
|
65
65
|
"react": "16.12.0",
|
|
@@ -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": "
|
|
87
|
+
"gitHead": "4c152ef7f2acafb450df245f5ce58638cbdb6be3"
|
|
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:
|
|
71
|
-
|
|
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.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
+
})
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
exports[`SharedStatus component should display the link if there is a link 1`] = `
|
|
4
4
|
<div>
|
|
5
5
|
<div
|
|
6
|
-
class="TwakeTheme--light
|
|
6
|
+
class="TwakeTheme--light u-dc"
|
|
7
7
|
>
|
|
8
8
|
<span
|
|
9
9
|
class="shared-status"
|
|
@@ -47,7 +47,7 @@ exports[`SharedStatus component should display the link if there is a link 1`] =
|
|
|
47
47
|
exports[`SharedStatus component should have the right display if there is several recipients 1`] = `
|
|
48
48
|
<div>
|
|
49
49
|
<div
|
|
50
|
-
class="TwakeTheme--light
|
|
50
|
+
class="TwakeTheme--light u-dc"
|
|
51
51
|
>
|
|
52
52
|
<span
|
|
53
53
|
class="shared-status"
|
|
@@ -84,7 +84,7 @@ exports[`SharedStatus component should have the right display if there is severa
|
|
|
84
84
|
exports[`SharedStatus component should just render a span if no sharing 1`] = `
|
|
85
85
|
<div>
|
|
86
86
|
<div
|
|
87
|
-
class="TwakeTheme--light
|
|
87
|
+
class="TwakeTheme--light u-dc"
|
|
88
88
|
>
|
|
89
89
|
<span
|
|
90
90
|
class="shared-status"
|