cozy-sharing 38.1.0 → 38.1.1

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,13 @@
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
+ ## [38.1.1](https://github.com/cozy/cozy-libs/compare/cozy-sharing@38.1.0...cozy-sharing@38.1.1) (2026-09-21)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **sharing:** Keep full share modal for nested-folder editors ([014c848](https://github.com/cozy/cozy-libs/commit/014c848bb46699cee47eb4baf8bb7a365edaf2f1))
11
+ - **sharing:** Show editable share modal for members with write access ([cba3a13](https://github.com/cozy/cozy-libs/commit/cba3a13185d1fe088136c3a758ec94f0d3472b69))
12
+
6
13
  # [38.1.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@38.0.0...cozy-sharing@38.1.0) (2026-09-16)
7
14
 
8
15
  ### Features
@@ -64,6 +64,7 @@ var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
64
64
 
65
65
  var _useSharingContext = useSharingContext(),
66
66
  canReshare = _useSharingContext.canReshare,
67
+ hasWriteAccess = _useSharingContext.hasWriteAccess,
67
68
  share = _useSharingContext.share,
68
69
  getSharingById = _useSharingContext.getSharingById,
69
70
  getSharingLink = _useSharingContext.getSharingLink,
@@ -125,7 +126,7 @@ var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
125
126
  }));
126
127
  var isInsideSharedDrive = Boolean((existingDocument === null || existingDocument === void 0 ? void 0 : existingDocument.driveId) && !isSharedDriveRoot);
127
128
  var isCurrentUserOwner = existingDocument !== null && existingDocument !== void 0 && existingDocument.driveId ? Boolean(sharedDriveSharing === null || sharedDriveSharing === void 0 || (_sharedDriveSharing$a2 = sharedDriveSharing.attributes) === null || _sharedDriveSharing$a2 === void 0 ? void 0 : _sharedDriveSharing$a2.owner) : documentId ? isOwner(documentId) : false;
128
- var isMemberReadOnly = isInsideSharedDrive && !isCurrentUserOwner;
129
+ var isMemberReadOnly = isInsideSharedDrive && !isCurrentUserOwner && !hasWriteAccess(documentId, existingDocument === null || existingDocument === void 0 ? void 0 : existingDocument.driveId);
129
130
  var canManageSharing = isCurrentUserOwner || (documentId ? canReshare(documentId) : false);
130
131
 
131
132
  var handleRevokeSelf = /*#__PURE__*/function () {
@@ -318,7 +319,7 @@ var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
318
319
  className: "u-ph-2"
319
320
  }, /*#__PURE__*/React.createElement(AntivirusAlert, {
320
321
  document: isSending ? frozenDoc : existingDocument
321
- }), isInsideSharedDrive ? /*#__PURE__*/React.createElement("div", {
322
+ }), isMemberReadOnly ? /*#__PURE__*/React.createElement("div", {
322
323
  className: styles['share-byemail-onlybylink']
323
324
  }, t('Files.share.shareByEmail.onlyByLink', {
324
325
  type: t("Files.share.shareByEmail.type.".concat((existingDocument === null || existingDocument === void 0 ? void 0 : existingDocument.type) === 'file' ? 'file' : 'folder'))
@@ -354,7 +355,7 @@ var FederatedFolderModalContent = function FederatedFolderModalContent(_ref) {
354
355
  documentType: "Files",
355
356
  showGenerateLinkButton: showGenerateLinkButton,
356
357
  autoOpenShareRestriction: autoOpenShareRestriction
357
- }), !isInsideSharedDrive && /*#__PURE__*/React.createElement(Button, {
358
+ }), !isMemberReadOnly && /*#__PURE__*/React.createElement(Button, {
358
359
  variant: "primary",
359
360
  label: t('FederatedFolder.share'),
360
361
  busy: isSending,
@@ -20,6 +20,7 @@ var mockRevoke = jest.fn();
20
20
  var mockRevokeSelf = jest.fn();
21
21
  var mockIsOwner = jest.fn();
22
22
  var mockCanReshare = jest.fn();
23
+ var mockHasWriteAccess = jest.fn();
23
24
  var mockGetSharingLink = jest.fn();
24
25
  var mockGetFederatedShareLink = jest.fn();
25
26
  var mockGetSharingById = jest.fn();
@@ -42,6 +43,7 @@ jest.mock('../../hooks/useSharingContext', function () {
42
43
  revokeSelf: mockRevokeSelf,
43
44
  isOwner: mockIsOwner,
44
45
  canReshare: mockCanReshare,
46
+ hasWriteAccess: mockHasWriteAccess,
45
47
  getSharingLink: mockGetSharingLink,
46
48
  getFederatedShareLink: mockGetFederatedShareLink,
47
49
  getDocumentPermissions: mockGetDocumentPermissions,
@@ -142,6 +144,7 @@ describe('FederatedFolderModal', function () {
142
144
  mockRevokeSelf.mockResolvedValue();
143
145
  mockIsOwner.mockReturnValue(false);
144
146
  mockCanReshare.mockReturnValue(false);
147
+ mockHasWriteAccess.mockReturnValue(false);
145
148
  mockGetDocumentPermissions.mockReturnValue([]);
146
149
  mockFetchSharedDriveSharingLinks.mockResolvedValue([]);
147
150
  mockGetSharingLink.mockReturnValue('https://example.com/share/abc123');
@@ -319,11 +322,40 @@ describe('FederatedFolderModal', function () {
319
322
  }
320
323
  }, _callee6);
321
324
  })));
322
- it('should allow share by email when document is the federated shared folder root', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee7() {
325
+ it('should allow share by email when the current user has write access, even inside a federated shared folder', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee7() {
323
326
  var _setup7, findByText, queryByText;
324
327
 
325
328
  return _regeneratorRuntime.wrap(function _callee7$(_context7) {
326
329
  while (1) switch (_context7.prev = _context7.next) {
330
+ case 0:
331
+ mockHasWriteAccess.mockReturnValue(true);
332
+ _setup7 = setup({
333
+ document: _objectSpread(_objectSpread({}, mockDocument), {}, {
334
+ driveId: 'federated-folder-id',
335
+ type: 'directory'
336
+ })
337
+ }), findByText = _setup7.findByText, queryByText = _setup7.queryByText;
338
+ _context7.next = 4;
339
+ return findByText('Add users');
340
+
341
+ case 4:
342
+ _context7.next = 6;
343
+ return findByText('Done');
344
+
345
+ case 6:
346
+ expect(queryByText('This folder can only be shared by link, because')).toBe(null);
347
+
348
+ case 7:
349
+ case "end":
350
+ return _context7.stop();
351
+ }
352
+ }, _callee7);
353
+ })));
354
+ it('should allow share by email when document is the federated shared folder root', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee8() {
355
+ var _setup8, findByText, queryByText;
356
+
357
+ return _regeneratorRuntime.wrap(function _callee8$(_context8) {
358
+ while (1) switch (_context8.prev = _context8.next) {
327
359
  case 0:
328
360
  mockGetSharingById.mockReturnValue({
329
361
  attributes: {
@@ -334,13 +366,13 @@ describe('FederatedFolderModal', function () {
334
366
  }]
335
367
  }
336
368
  });
337
- _setup7 = setup({
369
+ _setup8 = setup({
338
370
  document: _objectSpread(_objectSpread({}, mockDocument), {}, {
339
371
  driveId: 'federated-folder-id',
340
372
  type: 'directory'
341
373
  })
342
- }), findByText = _setup7.findByText, queryByText = _setup7.queryByText;
343
- _context7.next = 4;
374
+ }), findByText = _setup8.findByText, queryByText = _setup8.queryByText;
375
+ _context8.next = 4;
344
376
  return findByText('Add users');
345
377
 
346
378
  case 4:
@@ -349,15 +381,15 @@ describe('FederatedFolderModal', function () {
349
381
 
350
382
  case 6:
351
383
  case "end":
352
- return _context7.stop();
384
+ return _context8.stop();
353
385
  }
354
- }, _callee7);
386
+ }, _callee8);
355
387
  })));
356
388
  });
357
389
  describe('shared drive link fetch on mount', function () {
358
- it('should fetch shared drive sharing links when document has a driveId and no permissions yet', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee8() {
359
- return _regeneratorRuntime.wrap(function _callee8$(_context8) {
360
- while (1) switch (_context8.prev = _context8.next) {
390
+ it('should fetch shared drive sharing links when document has a driveId and no permissions yet', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee9() {
391
+ return _regeneratorRuntime.wrap(function _callee9$(_context9) {
392
+ while (1) switch (_context9.prev = _context9.next) {
361
393
  case 0:
362
394
  mockGetDocumentPermissions.mockReturnValue([]);
363
395
  setup({
@@ -365,7 +397,7 @@ describe('FederatedFolderModal', function () {
365
397
  driveId: 'federated-folder-id'
366
398
  })
367
399
  });
368
- _context8.next = 4;
400
+ _context9.next = 4;
369
401
  return waitFor(function () {
370
402
  expect(mockFetchSharedDriveSharingLinks).toHaveBeenCalledTimes(1);
371
403
  });
@@ -378,14 +410,14 @@ describe('FederatedFolderModal', function () {
378
410
 
379
411
  case 5:
380
412
  case "end":
381
- return _context8.stop();
413
+ return _context9.stop();
382
414
  }
383
- }, _callee8);
415
+ }, _callee9);
384
416
  })));
385
- it('should use document.id when _id is missing', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee9() {
417
+ it('should use document.id when _id is missing', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10() {
386
418
  var document;
387
- return _regeneratorRuntime.wrap(function _callee9$(_context9) {
388
- while (1) switch (_context9.prev = _context9.next) {
419
+ return _regeneratorRuntime.wrap(function _callee10$(_context10) {
420
+ while (1) switch (_context10.prev = _context10.next) {
389
421
  case 0:
390
422
  document = {
391
423
  id: 'folder-id-only',
@@ -396,7 +428,7 @@ describe('FederatedFolderModal', function () {
396
428
  setup({
397
429
  document: document
398
430
  });
399
- _context9.next = 4;
431
+ _context10.next = 4;
400
432
  return waitFor(function () {
401
433
  expect(mockFetchSharedDriveSharingLinks).toHaveBeenCalledWith(document);
402
434
  });
@@ -406,20 +438,20 @@ describe('FederatedFolderModal', function () {
406
438
 
407
439
  case 5:
408
440
  case "end":
409
- return _context9.stop();
441
+ return _context10.stop();
410
442
  }
411
- }, _callee9);
443
+ }, _callee10);
412
444
  })));
413
- it('should not fetch shared drive sharing links when document has no driveId', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10() {
414
- var _setup8, findByText;
445
+ it('should not fetch shared drive sharing links when document has no driveId', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee11() {
446
+ var _setup9, findByText;
415
447
 
416
- return _regeneratorRuntime.wrap(function _callee10$(_context10) {
417
- while (1) switch (_context10.prev = _context10.next) {
448
+ return _regeneratorRuntime.wrap(function _callee11$(_context11) {
449
+ while (1) switch (_context11.prev = _context11.next) {
418
450
  case 0:
419
- _setup8 = setup({
451
+ _setup9 = setup({
420
452
  document: mockDocument
421
- }), findByText = _setup8.findByText;
422
- _context10.next = 3;
453
+ }), findByText = _setup9.findByText;
454
+ _context11.next = 3;
423
455
  return findByText('Copy link');
424
456
 
425
457
  case 3:
@@ -427,23 +459,23 @@ describe('FederatedFolderModal', function () {
427
459
 
428
460
  case 4:
429
461
  case "end":
430
- return _context10.stop();
462
+ return _context11.stop();
431
463
  }
432
- }, _callee10);
464
+ }, _callee11);
433
465
  })));
434
- it('should not refetch after one attempt when permissions stay empty', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee11() {
435
- var document, _setup9, rerender;
466
+ it('should not refetch after one attempt when permissions stay empty', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee12() {
467
+ var document, _setup10, rerender;
436
468
 
437
- return _regeneratorRuntime.wrap(function _callee11$(_context11) {
438
- while (1) switch (_context11.prev = _context11.next) {
469
+ return _regeneratorRuntime.wrap(function _callee12$(_context12) {
470
+ while (1) switch (_context12.prev = _context12.next) {
439
471
  case 0:
440
472
  document = _objectSpread(_objectSpread({}, mockDocument), {}, {
441
473
  driveId: 'federated-folder-id'
442
474
  });
443
- _setup9 = setup({
475
+ _setup10 = setup({
444
476
  document: document
445
- }), rerender = _setup9.rerender;
446
- _context11.next = 4;
477
+ }), rerender = _setup10.rerender;
478
+ _context12.next = 4;
447
479
  return waitFor(function () {
448
480
  expect(mockFetchSharedDriveSharingLinks).toHaveBeenCalledTimes(1);
449
481
  });
@@ -460,15 +492,15 @@ describe('FederatedFolderModal', function () {
460
492
 
461
493
  case 6:
462
494
  case "end":
463
- return _context11.stop();
495
+ return _context12.stop();
464
496
  }
465
- }, _callee11);
497
+ }, _callee12);
466
498
  })));
467
- it('should not fetch shared drive sharing links when permissions are already loaded', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee12() {
468
- var _setup10, findByText;
499
+ it('should not fetch shared drive sharing links when permissions are already loaded', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee13() {
500
+ var _setup11, findByText;
469
501
 
470
- return _regeneratorRuntime.wrap(function _callee12$(_context12) {
471
- while (1) switch (_context12.prev = _context12.next) {
502
+ return _regeneratorRuntime.wrap(function _callee13$(_context13) {
503
+ while (1) switch (_context13.prev = _context13.next) {
472
504
  case 0:
473
505
  mockGetDocumentPermissions.mockReturnValue([{
474
506
  id: 'perm-1',
@@ -479,12 +511,12 @@ describe('FederatedFolderModal', function () {
479
511
  }
480
512
  }
481
513
  }]);
482
- _setup10 = setup({
514
+ _setup11 = setup({
483
515
  document: _objectSpread(_objectSpread({}, mockDocument), {}, {
484
516
  driveId: 'federated-folder-id'
485
517
  })
486
- }), findByText = _setup10.findByText;
487
- _context12.next = 4;
518
+ }), findByText = _setup11.findByText;
519
+ _context13.next = 4;
488
520
  return findByText('Copy link');
489
521
 
490
522
  case 4:
@@ -492,9 +524,9 @@ describe('FederatedFolderModal', function () {
492
524
 
493
525
  case 5:
494
526
  case "end":
495
- return _context12.stop();
527
+ return _context13.stop();
496
528
  }
497
- }, _callee12);
529
+ }, _callee13);
498
530
  })));
499
531
  });
500
532
  describe('onSend callback', function () {
@@ -502,21 +534,21 @@ describe('FederatedFolderModal', function () {
502
534
  name: 'Alice',
503
535
  email: 'alice@example.com'
504
536
  };
505
- it('should call onClose and skip share when there are no pending recipients', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee13() {
506
- var _setup11, findByText, sendButton;
537
+ it('should call onClose and skip share when there are no pending recipients', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee14() {
538
+ var _setup12, findByText, sendButton;
507
539
 
508
- return _regeneratorRuntime.wrap(function _callee13$(_context13) {
509
- while (1) switch (_context13.prev = _context13.next) {
540
+ return _regeneratorRuntime.wrap(function _callee14$(_context14) {
541
+ while (1) switch (_context14.prev = _context14.next) {
510
542
  case 0:
511
543
  // pendingRecipients defaults to [] via beforeEach
512
- _setup11 = setup(), findByText = _setup11.findByText;
513
- _context13.next = 3;
544
+ _setup12 = setup(), findByText = _setup12.findByText;
545
+ _context14.next = 3;
514
546
  return findByText('Done');
515
547
 
516
548
  case 3:
517
- sendButton = _context13.sent;
549
+ sendButton = _context14.sent;
518
550
  fireEvent.click(sendButton);
519
- _context13.next = 7;
551
+ _context14.next = 7;
520
552
  return waitFor(function () {
521
553
  expect(mockOnClose).toHaveBeenCalled();
522
554
  expect(mockShare).not.toHaveBeenCalled();
@@ -524,15 +556,15 @@ describe('FederatedFolderModal', function () {
524
556
 
525
557
  case 7:
526
558
  case "end":
527
- return _context13.stop();
559
+ return _context14.stop();
528
560
  }
529
- }, _callee13);
561
+ }, _callee14);
530
562
  })));
531
- it('should call share with correct parameters', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee14() {
532
- var _setup12, findByText, sendButton;
563
+ it('should call share with correct parameters', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee15() {
564
+ var _setup13, findByText, sendButton;
533
565
 
534
- return _regeneratorRuntime.wrap(function _callee14$(_context14) {
535
- while (1) switch (_context14.prev = _context14.next) {
566
+ return _regeneratorRuntime.wrap(function _callee15$(_context15) {
567
+ while (1) switch (_context15.prev = _context15.next) {
536
568
  case 0:
537
569
  usePendingRecipients.mockReturnValue({
538
570
  pendingRecipients: [pendingRecipient],
@@ -542,14 +574,14 @@ describe('FederatedFolderModal', function () {
542
574
  });
543
575
  getOrCreateFromArray.mockResolvedValue([pendingRecipient]);
544
576
  mockShare.mockResolvedValueOnce({});
545
- _setup12 = setup(), findByText = _setup12.findByText;
546
- _context14.next = 6;
577
+ _setup13 = setup(), findByText = _setup13.findByText;
578
+ _context15.next = 6;
547
579
  return findByText('Done');
548
580
 
549
581
  case 6:
550
- sendButton = _context14.sent;
582
+ sendButton = _context15.sent;
551
583
  fireEvent.click(sendButton);
552
- _context14.next = 10;
584
+ _context15.next = 10;
553
585
  return waitFor(function () {
554
586
  expect(mockShare).toHaveBeenCalledWith({
555
587
  description: 'My Test Folder',
@@ -563,15 +595,15 @@ describe('FederatedFolderModal', function () {
563
595
 
564
596
  case 10:
565
597
  case "end":
566
- return _context14.stop();
598
+ return _context15.stop();
567
599
  }
568
- }, _callee14);
600
+ }, _callee15);
569
601
  })));
570
- it('should show success alert and close modal on successful share', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee15() {
571
- var _setup13, findByText, sendButton;
602
+ it('should show success alert and close modal on successful share', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee16() {
603
+ var _setup14, findByText, sendButton;
572
604
 
573
- return _regeneratorRuntime.wrap(function _callee15$(_context15) {
574
- while (1) switch (_context15.prev = _context15.next) {
605
+ return _regeneratorRuntime.wrap(function _callee16$(_context16) {
606
+ while (1) switch (_context16.prev = _context16.next) {
575
607
  case 0:
576
608
  usePendingRecipients.mockReturnValue({
577
609
  pendingRecipients: [pendingRecipient],
@@ -581,14 +613,14 @@ describe('FederatedFolderModal', function () {
581
613
  });
582
614
  getOrCreateFromArray.mockResolvedValue([pendingRecipient]);
583
615
  mockShare.mockResolvedValueOnce({});
584
- _setup13 = setup(), findByText = _setup13.findByText;
585
- _context15.next = 6;
616
+ _setup14 = setup(), findByText = _setup14.findByText;
617
+ _context16.next = 6;
586
618
  return findByText('Done');
587
619
 
588
620
  case 6:
589
- sendButton = _context15.sent;
621
+ sendButton = _context16.sent;
590
622
  fireEvent.click(sendButton);
591
- _context15.next = 10;
623
+ _context16.next = 10;
592
624
  return waitFor(function () {
593
625
  expect(mockShowAlert).toHaveBeenCalledWith({
594
626
  message: 'Folder has been shared',
@@ -600,15 +632,15 @@ describe('FederatedFolderModal', function () {
600
632
 
601
633
  case 10:
602
634
  case "end":
603
- return _context15.stop();
635
+ return _context16.stop();
604
636
  }
605
- }, _callee15);
637
+ }, _callee16);
606
638
  })));
607
- it('should show error alert when share fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee16() {
608
- var _setup14, findByText, sendButton;
639
+ it('should show error alert when share fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee17() {
640
+ var _setup15, findByText, sendButton;
609
641
 
610
- return _regeneratorRuntime.wrap(function _callee16$(_context16) {
611
- while (1) switch (_context16.prev = _context16.next) {
642
+ return _regeneratorRuntime.wrap(function _callee17$(_context17) {
643
+ while (1) switch (_context17.prev = _context17.next) {
612
644
  case 0:
613
645
  usePendingRecipients.mockReturnValue({
614
646
  pendingRecipients: [pendingRecipient],
@@ -618,14 +650,14 @@ describe('FederatedFolderModal', function () {
618
650
  });
619
651
  getOrCreateFromArray.mockResolvedValue([pendingRecipient]);
620
652
  mockShare.mockRejectedValueOnce(new Error('Share failed'));
621
- _setup14 = setup(), findByText = _setup14.findByText;
622
- _context16.next = 6;
653
+ _setup15 = setup(), findByText = _setup15.findByText;
654
+ _context17.next = 6;
623
655
  return findByText('Done');
624
656
 
625
657
  case 6:
626
- sendButton = _context16.sent;
658
+ sendButton = _context17.sent;
627
659
  fireEvent.click(sendButton);
628
- _context16.next = 10;
660
+ _context17.next = 10;
629
661
  return waitFor(function () {
630
662
  expect(mockShowAlert).toHaveBeenCalledWith({
631
663
  message: 'Error while sharing folder',
@@ -637,15 +669,15 @@ describe('FederatedFolderModal', function () {
637
669
 
638
670
  case 10:
639
671
  case "end":
640
- return _context16.stop();
672
+ return _context17.stop();
641
673
  }
642
- }, _callee16);
674
+ }, _callee17);
643
675
  })));
644
- it('should show progress icon on Done button while share is in progress', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee18() {
645
- var resolveShare, _setup15, findByText, getByRole, queryByRole, sendButton;
676
+ it('should show progress icon on Done button while share is in progress', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee19() {
677
+ var resolveShare, _setup16, findByText, getByRole, queryByRole, sendButton;
646
678
 
647
- return _regeneratorRuntime.wrap(function _callee18$(_context18) {
648
- while (1) switch (_context18.prev = _context18.next) {
679
+ return _regeneratorRuntime.wrap(function _callee19$(_context19) {
680
+ while (1) switch (_context19.prev = _context19.next) {
649
681
  case 0:
650
682
  usePendingRecipients.mockReturnValue({
651
683
  pendingRecipients: [pendingRecipient],
@@ -657,14 +689,14 @@ describe('FederatedFolderModal', function () {
657
689
  mockShare.mockReturnValueOnce(new Promise(function (resolve) {
658
690
  resolveShare = resolve;
659
691
  }));
660
- _setup15 = setup(), findByText = _setup15.findByText, getByRole = _setup15.getByRole, queryByRole = _setup15.queryByRole;
661
- _context18.next = 6;
692
+ _setup16 = setup(), findByText = _setup16.findByText, getByRole = _setup16.getByRole, queryByRole = _setup16.queryByRole;
693
+ _context19.next = 6;
662
694
  return findByText('Done');
663
695
 
664
696
  case 6:
665
- sendButton = _context18.sent;
697
+ sendButton = _context19.sent;
666
698
  fireEvent.click(sendButton);
667
- _context18.next = 10;
699
+ _context19.next = 10;
668
700
  return waitFor(function () {
669
701
  expect(getByRole('button', {
670
702
  name: 'Done'
@@ -673,25 +705,25 @@ describe('FederatedFolderModal', function () {
673
705
  });
674
706
 
675
707
  case 10:
676
- _context18.next = 12;
677
- return act( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee17() {
678
- return _regeneratorRuntime.wrap(function _callee17$(_context17) {
679
- while (1) switch (_context17.prev = _context17.next) {
708
+ _context19.next = 12;
709
+ return act( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee18() {
710
+ return _regeneratorRuntime.wrap(function _callee18$(_context18) {
711
+ while (1) switch (_context18.prev = _context18.next) {
680
712
  case 0:
681
713
  resolveShare({});
682
714
 
683
715
  case 1:
684
716
  case "end":
685
- return _context17.stop();
717
+ return _context18.stop();
686
718
  }
687
- }, _callee17);
719
+ }, _callee18);
688
720
  })));
689
721
 
690
722
  case 12:
691
723
  case "end":
692
- return _context18.stop();
724
+ return _context19.stop();
693
725
  }
694
- }, _callee18);
726
+ }, _callee19);
695
727
  })));
696
728
  });
697
729
  describe('onRevoke callback', function () {
@@ -704,27 +736,27 @@ describe('FederatedFolderModal', function () {
704
736
  name: 'Me',
705
737
  email: 'me@test.com'
706
738
  };
707
- it('should call onRevokeSelf and onRevokeSuccess when the current user is revoked', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee19() {
708
- var onRevokeSuccess, _setup16, findByLabelText;
739
+ it('should call onRevokeSelf and onRevokeSuccess when the current user is revoked', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee20() {
740
+ var onRevokeSuccess, _setup17, findByLabelText;
709
741
 
710
- return _regeneratorRuntime.wrap(function _callee19$(_context19) {
711
- while (1) switch (_context19.prev = _context19.next) {
742
+ return _regeneratorRuntime.wrap(function _callee20$(_context20) {
743
+ while (1) switch (_context20.prev = _context20.next) {
712
744
  case 0:
713
745
  onRevokeSuccess = jest.fn();
714
746
  mockGetEffectiveRecipients.mockReturnValue([currentUserRecipient]);
715
- _setup16 = setup({
747
+ _setup17 = setup({
716
748
  onRevokeSuccess: onRevokeSuccess
717
- }), findByLabelText = _setup16.findByLabelText;
718
- _context19.t0 = fireEvent;
719
- _context19.next = 6;
749
+ }), findByLabelText = _setup17.findByLabelText;
750
+ _context20.t0 = fireEvent;
751
+ _context20.next = 6;
720
752
  return findByLabelText('Remove from sharing');
721
753
 
722
754
  case 6:
723
- _context19.t1 = _context19.sent;
755
+ _context20.t1 = _context20.sent;
724
756
 
725
- _context19.t0.click.call(_context19.t0, _context19.t1);
757
+ _context20.t0.click.call(_context20.t0, _context20.t1);
726
758
 
727
- _context19.next = 10;
759
+ _context20.next = 10;
728
760
  return waitFor(function () {
729
761
  expect(mockRevokeSelf).toHaveBeenCalledWith(mockDocument);
730
762
  expect(mockRevoke).not.toHaveBeenCalled();
@@ -733,15 +765,15 @@ describe('FederatedFolderModal', function () {
733
765
 
734
766
  case 10:
735
767
  case "end":
736
- return _context19.stop();
768
+ return _context20.stop();
737
769
  }
738
- }, _callee19);
770
+ }, _callee20);
739
771
  })));
740
- it('should allow a write recipient to revoke another recipient', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee20() {
741
- var otherRecipient, _setup17, findByLabelText;
772
+ it('should allow a write recipient to revoke another recipient', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee21() {
773
+ var otherRecipient, _setup18, findByLabelText;
742
774
 
743
- return _regeneratorRuntime.wrap(function _callee20$(_context20) {
744
- while (1) switch (_context20.prev = _context20.next) {
775
+ return _regeneratorRuntime.wrap(function _callee21$(_context21) {
776
+ while (1) switch (_context21.prev = _context21.next) {
745
777
  case 0:
746
778
  otherRecipient = {
747
779
  instance: 'https://other.mycozy.cloud',
@@ -754,17 +786,17 @@ describe('FederatedFolderModal', function () {
754
786
  };
755
787
  mockCanReshare.mockReturnValue(true);
756
788
  mockGetEffectiveRecipients.mockReturnValue([otherRecipient]);
757
- _setup17 = setup(), findByLabelText = _setup17.findByLabelText;
758
- _context20.t0 = fireEvent;
759
- _context20.next = 7;
789
+ _setup18 = setup(), findByLabelText = _setup18.findByLabelText;
790
+ _context21.t0 = fireEvent;
791
+ _context21.next = 7;
760
792
  return findByLabelText('Remove from sharing');
761
793
 
762
794
  case 7:
763
- _context20.t1 = _context20.sent;
795
+ _context21.t1 = _context21.sent;
764
796
 
765
- _context20.t0.click.call(_context20.t0, _context20.t1);
797
+ _context21.t0.click.call(_context21.t0, _context21.t1);
766
798
 
767
- _context20.next = 11;
799
+ _context21.next = 11;
768
800
  return waitFor(function () {
769
801
  expect(mockCanReshare).toHaveBeenCalledWith(mockDocument._id);
770
802
  expect(mockRevoke).toHaveBeenCalledWith(mockDocument, otherRecipient.sharingId, otherRecipient.memberIndex);
@@ -773,40 +805,40 @@ describe('FederatedFolderModal', function () {
773
805
 
774
806
  case 11:
775
807
  case "end":
776
- return _context20.stop();
808
+ return _context21.stop();
777
809
  }
778
- }, _callee20);
810
+ }, _callee21);
779
811
  })));
780
812
  });
781
813
  describe('onClose callback', function () {
782
- it('should call onClose when close button is clicked', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee21() {
783
- var _setup18, findByRole, closeButton;
814
+ it('should call onClose when close button is clicked', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee22() {
815
+ var _setup19, findByRole, closeButton;
784
816
 
785
- return _regeneratorRuntime.wrap(function _callee21$(_context21) {
786
- while (1) switch (_context21.prev = _context21.next) {
817
+ return _regeneratorRuntime.wrap(function _callee22$(_context22) {
818
+ while (1) switch (_context22.prev = _context22.next) {
787
819
  case 0:
788
- _setup18 = setup(), findByRole = _setup18.findByRole;
789
- _context21.next = 3;
820
+ _setup19 = setup(), findByRole = _setup19.findByRole;
821
+ _context22.next = 3;
790
822
  return findByRole('button', {
791
823
  name: /close/i
792
824
  });
793
825
 
794
826
  case 3:
795
- closeButton = _context21.sent;
827
+ closeButton = _context22.sent;
796
828
  fireEvent.click(closeButton);
797
829
  expect(mockOnClose).toHaveBeenCalled();
798
830
 
799
831
  case 6:
800
832
  case "end":
801
- return _context21.stop();
833
+ return _context22.stop();
802
834
  }
803
- }, _callee21);
835
+ }, _callee22);
804
836
  })));
805
- it('should ask confirmation when close button is clicked with pending recipients', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee22() {
806
- var _setup19, getByRole, findByRole, getByText, closeButton;
837
+ it('should ask confirmation when close button is clicked with pending recipients', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee23() {
838
+ var _setup20, getByRole, findByRole, getByText, closeButton;
807
839
 
808
- return _regeneratorRuntime.wrap(function _callee22$(_context22) {
809
- while (1) switch (_context22.prev = _context22.next) {
840
+ return _regeneratorRuntime.wrap(function _callee23$(_context23) {
841
+ while (1) switch (_context23.prev = _context23.next) {
810
842
  case 0:
811
843
  usePendingRecipients.mockReturnValue({
812
844
  pendingRecipients: [{
@@ -817,14 +849,14 @@ describe('FederatedFolderModal', function () {
817
849
  selectedOption: 'readWrite',
818
850
  setSelectedOption: jest.fn()
819
851
  });
820
- _setup19 = setup(), getByRole = _setup19.getByRole, findByRole = _setup19.findByRole, getByText = _setup19.getByText;
821
- _context22.next = 4;
852
+ _setup20 = setup(), getByRole = _setup20.getByRole, findByRole = _setup20.findByRole, getByText = _setup20.getByText;
853
+ _context23.next = 4;
822
854
  return findByRole('button', {
823
855
  name: /close/i
824
856
  });
825
857
 
826
858
  case 4:
827
- closeButton = _context22.sent;
859
+ closeButton = _context23.sent;
828
860
  fireEvent.click(closeButton);
829
861
  expect(getByText("Discard the changes you haven't saved?")).toBeTruthy();
830
862
  expect(mockOnClose).not.toHaveBeenCalled();
@@ -835,15 +867,15 @@ describe('FederatedFolderModal', function () {
835
867
 
836
868
  case 10:
837
869
  case "end":
838
- return _context22.stop();
870
+ return _context23.stop();
839
871
  }
840
- }, _callee22);
872
+ }, _callee23);
841
873
  })));
842
- it('should close after discard confirmation', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee23() {
843
- var _setup20, getByRole, findByRole, getByText, closeButton;
874
+ it('should close after discard confirmation', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee24() {
875
+ var _setup21, getByRole, findByRole, getByText, closeButton;
844
876
 
845
- return _regeneratorRuntime.wrap(function _callee23$(_context23) {
846
- while (1) switch (_context23.prev = _context23.next) {
877
+ return _regeneratorRuntime.wrap(function _callee24$(_context24) {
878
+ while (1) switch (_context24.prev = _context24.next) {
847
879
  case 0:
848
880
  usePendingRecipients.mockReturnValue({
849
881
  pendingRecipients: [{
@@ -854,14 +886,14 @@ describe('FederatedFolderModal', function () {
854
886
  selectedOption: 'readWrite',
855
887
  setSelectedOption: jest.fn()
856
888
  });
857
- _setup20 = setup(), getByRole = _setup20.getByRole, findByRole = _setup20.findByRole, getByText = _setup20.getByText;
858
- _context23.next = 4;
889
+ _setup21 = setup(), getByRole = _setup21.getByRole, findByRole = _setup21.findByRole, getByText = _setup21.getByText;
890
+ _context24.next = 4;
859
891
  return findByRole('button', {
860
892
  name: /close/i
861
893
  });
862
894
 
863
895
  case 4:
864
- closeButton = _context23.sent;
896
+ closeButton = _context24.sent;
865
897
  fireEvent.click(closeButton);
866
898
  expect(getByText("Discard the changes you haven't saved?")).toBeTruthy();
867
899
  fireEvent.click(getByRole('button', {
@@ -871,9 +903,9 @@ describe('FederatedFolderModal', function () {
871
903
 
872
904
  case 9:
873
905
  case "end":
874
- return _context23.stop();
906
+ return _context24.stop();
875
907
  }
876
- }, _callee23);
908
+ }, _callee24);
877
909
  })));
878
910
  });
879
911
  });
@@ -27,7 +27,7 @@ export var ShareModal = withLocales(function (props) {
27
27
  var _useSharingContext = useSharingContext(),
28
28
  byDocId = _useSharingContext.byDocId,
29
29
  isOwner = _useSharingContext.isOwner,
30
- canReshare = _useSharingContext.canReshare,
30
+ hasWriteAccess = _useSharingContext.hasWriteAccess,
31
31
  documentType = _useSharingContext.documentType,
32
32
  getRecipients = _useSharingContext.getRecipients,
33
33
  revokeSelf = _useSharingContext.revokeSelf,
@@ -56,7 +56,7 @@ export var ShareModal = withLocales(function (props) {
56
56
  };
57
57
  }();
58
58
 
59
- var isEditable = !byDocId[document.id] || isOwner(document.id) || canReshare(document.id);
59
+ var isEditable = !byDocId[document.id] || isOwner(document.id) || hasWriteAccess(document.id, document.driveId);
60
60
 
61
61
  if (isEditable) {
62
62
  var isFederatedMode = flag('drive.federated-shared-folder.enabled');
@@ -0,0 +1,79 @@
1
+ import { render } from '@testing-library/react';
2
+ import React from 'react';
3
+ import { MemoryRouter } from 'react-router';
4
+ import { ShareModal } from './ShareModal';
5
+ var mockIsOwner = jest.fn();
6
+ var mockHasWriteAccess = jest.fn();
7
+ jest.mock('cozy-flags');
8
+ jest.mock('../../hoc/withLocales', function () {
9
+ return function (Component) {
10
+ return Component;
11
+ };
12
+ });
13
+ jest.mock('../../hooks/useSharingContext', function () {
14
+ return {
15
+ useSharingContext: function useSharingContext() {
16
+ return {
17
+ byDocId: {
18
+ 'doc-1': {
19
+ sharings: ['sharing-1']
20
+ }
21
+ },
22
+ isOwner: mockIsOwner,
23
+ hasWriteAccess: mockHasWriteAccess,
24
+ documentType: 'Files',
25
+ getRecipients: jest.fn().mockReturnValue([]),
26
+ revokeSelf: jest.fn(),
27
+ allLoaded: false
28
+ };
29
+ }
30
+ };
31
+ });
32
+ jest.mock('./EditableSharingModal', function () {
33
+ return function () {
34
+ return /*#__PURE__*/React.createElement("div", null, "editable-modal");
35
+ };
36
+ });
37
+ jest.mock('./SharingDetailsModal', function () {
38
+ return function () {
39
+ return /*#__PURE__*/React.createElement("div", null, "details-modal");
40
+ };
41
+ });
42
+ jest.mock('../FederatedFolder/FederatedFolderModal', function () {
43
+ return function () {
44
+ return /*#__PURE__*/React.createElement("div", null, "federated-modal");
45
+ };
46
+ });
47
+ var mockDocument = {
48
+ id: 'doc-1',
49
+ driveId: undefined
50
+ };
51
+ describe('ShareModal dispatcher', function () {
52
+ beforeEach(function () {
53
+ mockIsOwner.mockReset();
54
+ mockHasWriteAccess.mockReset();
55
+ });
56
+ it('renders the editable modal when the current user has write access, even without reshare rights', function () {
57
+ mockIsOwner.mockReturnValue(false);
58
+ mockHasWriteAccess.mockReturnValue(true);
59
+
60
+ var _render = render( /*#__PURE__*/React.createElement(MemoryRouter, null, /*#__PURE__*/React.createElement(ShareModal, {
61
+ document: mockDocument
62
+ }))),
63
+ getByText = _render.getByText;
64
+
65
+ expect(getByText('editable-modal')).toBeTruthy();
66
+ expect(mockHasWriteAccess).toHaveBeenCalledWith('doc-1', undefined);
67
+ });
68
+ it('renders the restricted details modal when the current user has read-only access', function () {
69
+ mockIsOwner.mockReturnValue(false);
70
+ mockHasWriteAccess.mockReturnValue(false);
71
+
72
+ var _render2 = render( /*#__PURE__*/React.createElement(MemoryRouter, null, /*#__PURE__*/React.createElement(ShareModal, {
73
+ document: mockDocument
74
+ }))),
75
+ getByText = _render2.getByText;
76
+
77
+ expect(getByText('details-modal')).toBeTruthy();
78
+ });
79
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-sharing",
3
- "version": "38.1.0",
3
+ "version": "38.1.1",
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": "^140.5.0",
62
- "cozy-ui-plus": "^13.0.4",
62
+ "cozy-ui-plus": "^13.0.5",
63
63
  "jest": "30.3.0",
64
64
  "jest-environment-jsdom": "30.3.0",
65
65
  "react": "16.12.0",
@@ -85,5 +85,5 @@
85
85
  "sideEffects": [
86
86
  "*.css"
87
87
  ],
88
- "gitHead": "96598a165eae6c8eef3da4b6451af1dfdb8801a0"
88
+ "gitHead": "368b1e85eb1f6a6a0731681ef2a38a7bbc3fe779"
89
89
  }
@@ -42,6 +42,7 @@ const FederatedFolderModalContent = ({
42
42
  const { t } = useI18n()
43
43
  const {
44
44
  canReshare,
45
+ hasWriteAccess,
45
46
  share,
46
47
  getSharingById,
47
48
  getSharingLink,
@@ -99,7 +100,10 @@ const FederatedFolderModalContent = ({
99
100
  : documentId
100
101
  ? isOwner(documentId)
101
102
  : false
102
- const isMemberReadOnly = isInsideSharedDrive && !isCurrentUserOwner
103
+ const isMemberReadOnly =
104
+ isInsideSharedDrive &&
105
+ !isCurrentUserOwner &&
106
+ !hasWriteAccess(documentId, existingDocument?.driveId)
103
107
  const canManageSharing =
104
108
  isCurrentUserOwner || (documentId ? canReshare(documentId) : false)
105
109
 
@@ -239,7 +243,7 @@ const FederatedFolderModalContent = ({
239
243
  <AntivirusAlert
240
244
  document={isSending ? frozenDoc : existingDocument}
241
245
  />
242
- {isInsideSharedDrive ? (
246
+ {isMemberReadOnly ? (
243
247
  <div className={styles['share-byemail-onlybylink']}>
244
248
  {t('Files.share.shareByEmail.onlyByLink', {
245
249
  type: t(
@@ -293,7 +297,7 @@ const FederatedFolderModalContent = ({
293
297
  showGenerateLinkButton={showGenerateLinkButton}
294
298
  autoOpenShareRestriction={autoOpenShareRestriction}
295
299
  />
296
- {!isInsideSharedDrive && (
300
+ {!isMemberReadOnly && (
297
301
  <Button
298
302
  variant="primary"
299
303
  label={t('FederatedFolder.share')}
@@ -14,6 +14,7 @@ const mockRevoke = jest.fn()
14
14
  const mockRevokeSelf = jest.fn()
15
15
  const mockIsOwner = jest.fn()
16
16
  const mockCanReshare = jest.fn()
17
+ const mockHasWriteAccess = jest.fn()
17
18
  const mockGetSharingLink = jest.fn()
18
19
  const mockGetFederatedShareLink = jest.fn()
19
20
  const mockGetSharingById = jest.fn()
@@ -34,6 +35,7 @@ jest.mock('../../hooks/useSharingContext', () => ({
34
35
  revokeSelf: mockRevokeSelf,
35
36
  isOwner: mockIsOwner,
36
37
  canReshare: mockCanReshare,
38
+ hasWriteAccess: mockHasWriteAccess,
37
39
  getSharingLink: mockGetSharingLink,
38
40
  getFederatedShareLink: mockGetFederatedShareLink,
39
41
  getDocumentPermissions: mockGetDocumentPermissions,
@@ -115,6 +117,7 @@ describe('FederatedFolderModal', () => {
115
117
  mockRevokeSelf.mockResolvedValue()
116
118
  mockIsOwner.mockReturnValue(false)
117
119
  mockCanReshare.mockReturnValue(false)
120
+ mockHasWriteAccess.mockReturnValue(false)
118
121
  mockGetDocumentPermissions.mockReturnValue([])
119
122
  mockFetchSharedDriveSharingLinks.mockResolvedValue([])
120
123
  mockGetSharingLink.mockReturnValue('https://example.com/share/abc123')
@@ -224,6 +227,25 @@ describe('FederatedFolderModal', () => {
224
227
  await findByText('it has a shared parent')
225
228
  })
226
229
 
230
+ it('should allow share by email when the current user has write access, even inside a federated shared folder', async () => {
231
+ mockHasWriteAccess.mockReturnValue(true)
232
+
233
+ const { findByText, queryByText } = setup({
234
+ document: {
235
+ ...mockDocument,
236
+ driveId: 'federated-folder-id',
237
+ type: 'directory'
238
+ }
239
+ })
240
+
241
+ await findByText('Add users')
242
+ await findByText('Done')
243
+
244
+ expect(
245
+ queryByText('This folder can only be shared by link, because')
246
+ ).toBe(null)
247
+ })
248
+
227
249
  it('should allow share by email when document is the federated shared folder root', async () => {
228
250
  mockGetSharingById.mockReturnValue({
229
251
  attributes: {
@@ -20,7 +20,7 @@ export const ShareModal = withLocales(props => {
20
20
  const {
21
21
  byDocId,
22
22
  isOwner,
23
- canReshare,
23
+ hasWriteAccess,
24
24
  documentType,
25
25
  getRecipients,
26
26
  revokeSelf,
@@ -33,7 +33,9 @@ export const ShareModal = withLocales(props => {
33
33
  }
34
34
 
35
35
  const isEditable =
36
- !byDocId[document.id] || isOwner(document.id) || canReshare(document.id)
36
+ !byDocId[document.id] ||
37
+ isOwner(document.id) ||
38
+ hasWriteAccess(document.id, document.driveId)
37
39
 
38
40
  if (isEditable) {
39
41
  const isFederatedMode = flag('drive.federated-shared-folder.enabled')
@@ -0,0 +1,66 @@
1
+ import { render } from '@testing-library/react'
2
+ import React from 'react'
3
+ import { MemoryRouter } from 'react-router'
4
+
5
+ import { ShareModal } from './ShareModal'
6
+
7
+ const mockIsOwner = jest.fn()
8
+ const mockHasWriteAccess = jest.fn()
9
+
10
+ jest.mock('cozy-flags')
11
+
12
+ jest.mock('../../hoc/withLocales', () => Component => Component)
13
+
14
+ jest.mock('../../hooks/useSharingContext', () => ({
15
+ useSharingContext: () => ({
16
+ byDocId: { 'doc-1': { sharings: ['sharing-1'] } },
17
+ isOwner: mockIsOwner,
18
+ hasWriteAccess: mockHasWriteAccess,
19
+ documentType: 'Files',
20
+ getRecipients: jest.fn().mockReturnValue([]),
21
+ revokeSelf: jest.fn(),
22
+ allLoaded: false
23
+ })
24
+ }))
25
+
26
+ jest.mock('./EditableSharingModal', () => () => <div>editable-modal</div>)
27
+ jest.mock('./SharingDetailsModal', () => () => <div>details-modal</div>)
28
+ jest.mock('../FederatedFolder/FederatedFolderModal', () => () => (
29
+ <div>federated-modal</div>
30
+ ))
31
+
32
+ const mockDocument = { id: 'doc-1', driveId: undefined }
33
+
34
+ describe('ShareModal dispatcher', () => {
35
+ beforeEach(() => {
36
+ mockIsOwner.mockReset()
37
+ mockHasWriteAccess.mockReset()
38
+ })
39
+
40
+ it('renders the editable modal when the current user has write access, even without reshare rights', () => {
41
+ mockIsOwner.mockReturnValue(false)
42
+ mockHasWriteAccess.mockReturnValue(true)
43
+
44
+ const { getByText } = render(
45
+ <MemoryRouter>
46
+ <ShareModal document={mockDocument} />
47
+ </MemoryRouter>
48
+ )
49
+
50
+ expect(getByText('editable-modal')).toBeTruthy()
51
+ expect(mockHasWriteAccess).toHaveBeenCalledWith('doc-1', undefined)
52
+ })
53
+
54
+ it('renders the restricted details modal when the current user has read-only access', () => {
55
+ mockIsOwner.mockReturnValue(false)
56
+ mockHasWriteAccess.mockReturnValue(false)
57
+
58
+ const { getByText } = render(
59
+ <MemoryRouter>
60
+ <ShareModal document={mockDocument} />
61
+ </MemoryRouter>
62
+ )
63
+
64
+ expect(getByText('details-modal')).toBeTruthy()
65
+ })
66
+ })