cozy-sharing 33.6.0 → 33.6.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,12 @@
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
+ ## [33.6.1](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.6.0...cozy-sharing@33.6.1) (2026-06-22)
7
+
8
+ ### Bug Fixes
9
+
10
+ - Forget owner-only shared drive sharings ([795eff2](https://github.com/cozy/cozy-libs/commit/795eff246218780d6315ae49d4b55dbe732c450b))
11
+
6
12
  # [33.6.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.5.1...cozy-sharing@33.6.0) (2026-06-17)
7
13
 
8
14
  ### Features
@@ -11,7 +11,7 @@ import React from 'react';
11
11
  import { createMockClient } from 'cozy-client';
12
12
  import { SharingProvider } from './SharingProvider';
13
13
  import SharingContext from './context';
14
- import reducer, { addSharingLink, getDocumentPermissions } from './state';
14
+ import reducer, { addSharingLink, getDocumentPermissions, getDocumentSharing, receiveSharings } from './state';
15
15
  import AppLike from '../test/AppLike';
16
16
 
17
17
  var AppWrapper = function AppWrapper(_ref) {
@@ -411,6 +411,135 @@ describe('updateDocumentPermissions', function () {
411
411
  }, _callee7);
412
412
  })));
413
413
  });
414
+ describe('shared drive recipient revocation', function () {
415
+ var SHARED_DRIVE = {
416
+ _id: 'sharing_123',
417
+ id: 'sharing_123',
418
+ type: 'io.cozy.sharings',
419
+ attributes: {
420
+ drive: true,
421
+ owner: true,
422
+ description: 'Shared folder',
423
+ rules: [{
424
+ title: 'Shared folder',
425
+ doctype: 'io.cozy.files',
426
+ values: ['folder_123'],
427
+ add: 'none',
428
+ update: 'none',
429
+ remove: 'none'
430
+ }],
431
+ members: [{
432
+ status: 'owner',
433
+ email: 'owner@cozy.local',
434
+ instance: 'http://cozy.local'
435
+ }, {
436
+ status: 'pending',
437
+ email: 'recipient@cozy.local',
438
+ instance: 'http://recipient.cozy.local'
439
+ }]
440
+ }
441
+ };
442
+
443
+ var NEW_SHARING = _objectSpread(_objectSpread({}, SHARED_DRIVE), {}, {
444
+ _id: 'sharing_456',
445
+ id: 'sharing_456'
446
+ });
447
+
448
+ var document = {
449
+ _id: 'folder_123',
450
+ id: 'folder_123',
451
+ path: '/Shared folder'
452
+ };
453
+ var recipient = {
454
+ _id: 'contact_123',
455
+ id: 'contact_123',
456
+ _type: 'io.cozy.contacts',
457
+ email: 'recipient@cozy.local'
458
+ };
459
+ var provider;
460
+ var sharingCol;
461
+ beforeEach(function () {
462
+ var mockClient = createMockClient({});
463
+
464
+ mockClient.getStackClient = function () {
465
+ return {
466
+ uri: 'http://cozy.local'
467
+ };
468
+ };
469
+
470
+ mockClient.collection = jest.fn().mockReturnValue({});
471
+ provider = new SharingProvider({
472
+ client: mockClient
473
+ });
474
+ provider.state = _objectSpread(_objectSpread({}, provider.state), reducer(undefined, receiveSharings({
475
+ sharings: [SHARED_DRIVE]
476
+ })));
477
+ provider.dispatch = jest.fn(function (action) {
478
+ provider.state = _objectSpread(_objectSpread({}, provider.state), reducer(provider.state, action));
479
+ });
480
+ sharingCol = {
481
+ addRecipients: jest.fn(),
482
+ create: jest.fn().mockResolvedValue({
483
+ data: NEW_SHARING
484
+ }),
485
+ revokeRecipient: jest.fn().mockResolvedValue({})
486
+ };
487
+ provider.sharingCol = sharingCol;
488
+ });
489
+ it('forgets the shared drive when revoking its last recipient', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee8() {
490
+ return _regeneratorRuntime.wrap(function _callee8$(_context8) {
491
+ while (1) switch (_context8.prev = _context8.next) {
492
+ case 0:
493
+ _context8.next = 2;
494
+ return provider.revoke(document, SHARED_DRIVE.id, 1);
495
+
496
+ case 2:
497
+ expect(sharingCol.revokeRecipient).toHaveBeenCalledWith(SHARED_DRIVE, 1);
498
+ expect(getDocumentSharing(provider.state, document.id)).toBeNull();
499
+
500
+ case 4:
501
+ case "end":
502
+ return _context8.stop();
503
+ }
504
+ }, _callee8);
505
+ })));
506
+ it('creates a new sharing when re-adding after the last recipient was revoked', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee9() {
507
+ return _regeneratorRuntime.wrap(function _callee9$(_context9) {
508
+ while (1) switch (_context9.prev = _context9.next) {
509
+ case 0:
510
+ _context9.next = 2;
511
+ return provider.revoke(document, SHARED_DRIVE.id, 1);
512
+
513
+ case 2:
514
+ _context9.next = 4;
515
+ return provider.share({
516
+ document: document,
517
+ recipients: [recipient],
518
+ readOnlyRecipients: [],
519
+ description: 'Shared folder',
520
+ openSharing: true,
521
+ sharedDrive: true
522
+ });
523
+
524
+ case 4:
525
+ expect(sharingCol.addRecipients).not.toHaveBeenCalled();
526
+ expect(sharingCol.create).toHaveBeenCalledWith({
527
+ document: document,
528
+ recipients: [recipient],
529
+ readOnlyRecipients: [],
530
+ description: 'Shared folder',
531
+ previewPath: '/preview',
532
+ openSharing: true,
533
+ sharedDrive: true
534
+ });
535
+
536
+ case 6:
537
+ case "end":
538
+ return _context9.stop();
539
+ }
540
+ }, _callee9);
541
+ })));
542
+ });
414
543
  describe('fetchSharedDriveSharingLinks', function () {
415
544
  var PERM_DRIVE_FILE = {
416
545
  type: 'io.cozy.permissions',
@@ -434,10 +563,10 @@ describe('fetchSharedDriveSharingLinks', function () {
434
563
  id: 'file_in_drive',
435
564
  driveId: 'drive_123'
436
565
  };
437
- it('fetches shared drive sharing links by file id', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee8() {
566
+ it('fetches shared drive sharing links by file id', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10() {
438
567
  var mockFindLinksByIds, mockClient, provider, result, permissions;
439
- return _regeneratorRuntime.wrap(function _callee8$(_context8) {
440
- while (1) switch (_context8.prev = _context8.next) {
568
+ return _regeneratorRuntime.wrap(function _callee10$(_context10) {
569
+ while (1) switch (_context10.prev = _context10.next) {
441
570
  case 0:
442
571
  mockFindLinksByIds = jest.fn().mockResolvedValue({
443
572
  data: [PERM_DRIVE_FILE]
@@ -453,11 +582,11 @@ describe('fetchSharedDriveSharingLinks', function () {
453
582
  provider.dispatch = jest.fn(function (action) {
454
583
  provider.state = reducer(provider.state, action);
455
584
  });
456
- _context8.next = 8;
585
+ _context10.next = 8;
457
586
  return provider.fetchSharedDriveSharingLinks(driveFile);
458
587
 
459
588
  case 8:
460
- result = _context8.sent;
589
+ result = _context10.sent;
461
590
  expect(mockClient.collection).toHaveBeenCalledWith('io.cozy.permissions', {
462
591
  driveId: 'drive_123'
463
592
  });
@@ -470,14 +599,14 @@ describe('fetchSharedDriveSharingLinks', function () {
470
599
 
471
600
  case 16:
472
601
  case "end":
473
- return _context8.stop();
602
+ return _context10.stop();
474
603
  }
475
- }, _callee8);
604
+ }, _callee10);
476
605
  })));
477
- it('returns an empty array and skips the API call for non-shared-drive documents', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee9() {
606
+ it('returns an empty array and skips the API call for non-shared-drive documents', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee11() {
478
607
  var mockClient, findLinksByIds, provider, result;
479
- return _regeneratorRuntime.wrap(function _callee9$(_context9) {
480
- while (1) switch (_context9.prev = _context9.next) {
608
+ return _regeneratorRuntime.wrap(function _callee11$(_context11) {
609
+ while (1) switch (_context11.prev = _context11.next) {
481
610
  case 0:
482
611
  mockClient = createMockClient({});
483
612
  findLinksByIds = jest.fn();
@@ -488,27 +617,27 @@ describe('fetchSharedDriveSharingLinks', function () {
488
617
  client: mockClient
489
618
  });
490
619
  provider.dispatch = jest.fn();
491
- _context9.next = 7;
620
+ _context11.next = 7;
492
621
  return provider.fetchSharedDriveSharingLinks({
493
622
  _id: 'regular_file'
494
623
  });
495
624
 
496
625
  case 7:
497
- result = _context9.sent;
626
+ result = _context11.sent;
498
627
  expect(result).toEqual([]);
499
628
  expect(findLinksByIds).not.toHaveBeenCalled();
500
629
  expect(provider.dispatch).not.toHaveBeenCalled();
501
630
 
502
631
  case 11:
503
632
  case "end":
504
- return _context9.stop();
633
+ return _context11.stop();
505
634
  }
506
- }, _callee9);
635
+ }, _callee11);
507
636
  })));
508
- it('uses document.id as a fallback when _id is missing', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10() {
637
+ it('uses document.id as a fallback when _id is missing', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee12() {
509
638
  var mockFindLinksByIds, mockClient, provider;
510
- return _regeneratorRuntime.wrap(function _callee10$(_context10) {
511
- while (1) switch (_context10.prev = _context10.next) {
639
+ return _regeneratorRuntime.wrap(function _callee12$(_context12) {
640
+ while (1) switch (_context12.prev = _context12.next) {
512
641
  case 0:
513
642
  mockFindLinksByIds = jest.fn().mockResolvedValue({
514
643
  data: [PERM_DRIVE_FILE]
@@ -524,7 +653,7 @@ describe('fetchSharedDriveSharingLinks', function () {
524
653
  provider.dispatch = jest.fn(function (action) {
525
654
  provider.state = reducer(provider.state, action);
526
655
  });
527
- _context10.next = 8;
656
+ _context12.next = 8;
528
657
  return provider.fetchSharedDriveSharingLinks({
529
658
  id: 'file_in_drive',
530
659
  driveId: 'drive_123'
@@ -535,14 +664,14 @@ describe('fetchSharedDriveSharingLinks', function () {
535
664
 
536
665
  case 9:
537
666
  case "end":
538
- return _context10.stop();
667
+ return _context12.stop();
539
668
  }
540
- }, _callee10);
669
+ }, _callee12);
541
670
  })));
542
- it('does not dispatch when the response has no data', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee11() {
671
+ it('does not dispatch when the response has no data', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee13() {
543
672
  var mockFindLinksByIds, mockClient, provider, result;
544
- return _regeneratorRuntime.wrap(function _callee11$(_context11) {
545
- while (1) switch (_context11.prev = _context11.next) {
673
+ return _regeneratorRuntime.wrap(function _callee13$(_context13) {
674
+ while (1) switch (_context13.prev = _context13.next) {
546
675
  case 0:
547
676
  mockFindLinksByIds = jest.fn().mockResolvedValue({
548
677
  data: []
@@ -555,19 +684,19 @@ describe('fetchSharedDriveSharingLinks', function () {
555
684
  client: mockClient
556
685
  });
557
686
  provider.dispatch = jest.fn();
558
- _context11.next = 7;
687
+ _context13.next = 7;
559
688
  return provider.fetchSharedDriveSharingLinks(driveFile);
560
689
 
561
690
  case 7:
562
- result = _context11.sent;
691
+ result = _context13.sent;
563
692
  expect(result).toEqual([]);
564
693
  expect(provider.dispatch).not.toHaveBeenCalled();
565
694
 
566
695
  case 10:
567
696
  case "end":
568
- return _context11.stop();
697
+ return _context13.stop();
569
698
  }
570
- }, _callee11);
699
+ }, _callee13);
571
700
  })));
572
701
  });
573
702
  describe('shareByLink shared drive 409 recovery', function () {
@@ -593,10 +722,10 @@ describe('shareByLink shared drive 409 recovery', function () {
593
722
  id: 'file_in_drive',
594
723
  driveId: 'drive_123'
595
724
  };
596
- it('falls back to fetching existing links when create returns 409', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee12() {
725
+ it('falls back to fetching existing links when create returns 409', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee14() {
597
726
  var conflict, mockCreateSharingLink, mockFindLinksByIds, mockClient, provider, resp;
598
- return _regeneratorRuntime.wrap(function _callee12$(_context12) {
599
- while (1) switch (_context12.prev = _context12.next) {
727
+ return _regeneratorRuntime.wrap(function _callee14$(_context14) {
728
+ while (1) switch (_context14.prev = _context14.next) {
600
729
  case 0:
601
730
  conflict = Object.assign(new Error('Conflict'), {
602
731
  status: 409
@@ -617,27 +746,27 @@ describe('shareByLink shared drive 409 recovery', function () {
617
746
  provider.dispatch = jest.fn(function (action) {
618
747
  provider.state = reducer(provider.state, action);
619
748
  });
620
- _context12.next = 10;
749
+ _context14.next = 10;
621
750
  return provider.shareByLink(driveFile, {
622
751
  verbs: ['GET']
623
752
  });
624
753
 
625
754
  case 10:
626
- resp = _context12.sent;
755
+ resp = _context14.sent;
627
756
  expect(mockCreateSharingLink).toHaveBeenCalled();
628
757
  expect(mockFindLinksByIds).toHaveBeenCalledWith(['file_in_drive']);
629
758
  expect(resp.data.id).toBe(PERM_DRIVE_FILE.id);
630
759
 
631
760
  case 14:
632
761
  case "end":
633
- return _context12.stop();
762
+ return _context14.stop();
634
763
  }
635
- }, _callee12);
764
+ }, _callee14);
636
765
  })));
637
- it('does not duplicate existing links when 409 recovery refetches them', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee13() {
766
+ it('does not duplicate existing links when 409 recovery refetches them', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee15() {
638
767
  var conflict, mockCreateSharingLink, mockFindLinksByIds, mockClient, provider, resp, permissions;
639
- return _regeneratorRuntime.wrap(function _callee13$(_context13) {
640
- while (1) switch (_context13.prev = _context13.next) {
768
+ return _regeneratorRuntime.wrap(function _callee15$(_context15) {
769
+ while (1) switch (_context15.prev = _context15.next) {
641
770
  case 0:
642
771
  conflict = Object.assign(new Error('Conflict'), {
643
772
  status: 409
@@ -658,13 +787,13 @@ describe('shareByLink shared drive 409 recovery', function () {
658
787
  provider.dispatch = jest.fn(function (action) {
659
788
  provider.state = reducer(provider.state, action);
660
789
  });
661
- _context13.next = 10;
790
+ _context15.next = 10;
662
791
  return provider.shareByLink(driveFile, {
663
792
  verbs: ['GET']
664
793
  });
665
794
 
666
795
  case 10:
667
- resp = _context13.sent;
796
+ resp = _context15.sent;
668
797
  permissions = getDocumentPermissions(provider.state, 'file_in_drive');
669
798
  expect(resp.data.id).toBe(PERM_DRIVE_FILE.id);
670
799
  expect(provider.dispatch).not.toHaveBeenCalled();
@@ -672,14 +801,14 @@ describe('shareByLink shared drive 409 recovery', function () {
672
801
 
673
802
  case 15:
674
803
  case "end":
675
- return _context13.stop();
804
+ return _context15.stop();
676
805
  }
677
- }, _callee13);
806
+ }, _callee15);
678
807
  })));
679
- it('rethrows the original error when 409 happens and no link is found', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee14() {
808
+ it('rethrows the original error when 409 happens and no link is found', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee16() {
680
809
  var conflict, mockCreateSharingLink, mockFindLinksByIds, mockClient, provider;
681
- return _regeneratorRuntime.wrap(function _callee14$(_context14) {
682
- while (1) switch (_context14.prev = _context14.next) {
810
+ return _regeneratorRuntime.wrap(function _callee16$(_context16) {
811
+ while (1) switch (_context16.prev = _context16.next) {
683
812
  case 0:
684
813
  conflict = Object.assign(new Error('Conflict'), {
685
814
  status: 409
@@ -698,16 +827,16 @@ describe('shareByLink shared drive 409 recovery', function () {
698
827
  });
699
828
  provider.state = reducer();
700
829
  provider.dispatch = jest.fn();
701
- _context14.next = 10;
830
+ _context16.next = 10;
702
831
  return expect(provider.shareByLink(driveFile, {
703
832
  verbs: ['GET']
704
833
  })).rejects.toBe(conflict);
705
834
 
706
835
  case 10:
707
836
  case "end":
708
- return _context14.stop();
837
+ return _context16.stop();
709
838
  }
710
- }, _callee14);
839
+ }, _callee16);
711
840
  })));
712
841
  });
713
842
  describe('updateSharingMemberType', function () {
@@ -748,37 +877,37 @@ describe('updateSharingMemberType', function () {
748
877
  instance.state = _objectSpread(_objectSpread({}, instance.state), reducer(instance.state, action));
749
878
  });
750
879
  });
751
- it('should throw when sharing is not found', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee15() {
752
- return _regeneratorRuntime.wrap(function _callee15$(_context15) {
753
- while (1) switch (_context15.prev = _context15.next) {
880
+ it('should throw when sharing is not found', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee17() {
881
+ return _regeneratorRuntime.wrap(function _callee17$(_context17) {
882
+ while (1) switch (_context17.prev = _context17.next) {
754
883
  case 0:
755
- _context15.next = 2;
884
+ _context17.next = 2;
756
885
  return expect(instance.updateSharingMemberType('unknown-id', 1, 'one-way')).rejects.toThrow('Sharing not found');
757
886
 
758
887
  case 2:
759
888
  case "end":
760
- return _context15.stop();
889
+ return _context17.stop();
761
890
  }
762
- }, _callee15);
891
+ }, _callee17);
763
892
  })));
764
- it('should throw when member is not found', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee16() {
765
- return _regeneratorRuntime.wrap(function _callee16$(_context16) {
766
- while (1) switch (_context16.prev = _context16.next) {
893
+ it('should throw when member is not found', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee18() {
894
+ return _regeneratorRuntime.wrap(function _callee18$(_context18) {
895
+ while (1) switch (_context18.prev = _context18.next) {
767
896
  case 0:
768
- _context16.next = 2;
897
+ _context18.next = 2;
769
898
  return expect(instance.updateSharingMemberType('sharing-123', 99, 'one-way')).rejects.toThrow('Member not found');
770
899
 
771
900
  case 2:
772
901
  case "end":
773
- return _context16.stop();
902
+ return _context18.stop();
774
903
  }
775
- }, _callee16);
904
+ }, _callee18);
776
905
  })));
777
- it('should call setReadOnly when switching to one-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee17() {
778
- return _regeneratorRuntime.wrap(function _callee17$(_context17) {
779
- while (1) switch (_context17.prev = _context17.next) {
906
+ it('should call setReadOnly when switching to one-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee19() {
907
+ return _regeneratorRuntime.wrap(function _callee19$(_context19) {
908
+ while (1) switch (_context19.prev = _context19.next) {
780
909
  case 0:
781
- _context17.next = 2;
910
+ _context19.next = 2;
782
911
  return instance.updateSharingMemberType('sharing-123', 1, 'one-way');
783
912
 
784
913
  case 2:
@@ -787,15 +916,15 @@ describe('updateSharingMemberType', function () {
787
916
 
788
917
  case 4:
789
918
  case "end":
790
- return _context17.stop();
919
+ return _context19.stop();
791
920
  }
792
- }, _callee17);
921
+ }, _callee19);
793
922
  })));
794
- it('should call setReadWrite when switching to two-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee18() {
795
- return _regeneratorRuntime.wrap(function _callee18$(_context18) {
796
- while (1) switch (_context18.prev = _context18.next) {
923
+ it('should call setReadWrite when switching to two-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee20() {
924
+ return _regeneratorRuntime.wrap(function _callee20$(_context20) {
925
+ while (1) switch (_context20.prev = _context20.next) {
797
926
  case 0:
798
- _context18.next = 2;
927
+ _context20.next = 2;
799
928
  return instance.updateSharingMemberType('sharing-123', 1, 'two-way');
800
929
 
801
930
  case 2:
@@ -804,15 +933,15 @@ describe('updateSharingMemberType', function () {
804
933
 
805
934
  case 4:
806
935
  case "end":
807
- return _context18.stop();
936
+ return _context20.stop();
808
937
  }
809
- }, _callee18);
938
+ }, _callee20);
810
939
  })));
811
- it('should optimistically dispatch a state update when switching to one-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee19() {
812
- return _regeneratorRuntime.wrap(function _callee19$(_context19) {
813
- while (1) switch (_context19.prev = _context19.next) {
940
+ it('should optimistically dispatch a state update when switching to one-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee21() {
941
+ return _regeneratorRuntime.wrap(function _callee21$(_context21) {
942
+ while (1) switch (_context21.prev = _context21.next) {
814
943
  case 0:
815
- _context19.next = 2;
944
+ _context21.next = 2;
816
945
  return instance.updateSharingMemberType('sharing-123', 1, 'one-way');
817
946
 
818
947
  case 2:
@@ -829,15 +958,15 @@ describe('updateSharingMemberType', function () {
829
958
 
830
959
  case 3:
831
960
  case "end":
832
- return _context19.stop();
961
+ return _context21.stop();
833
962
  }
834
- }, _callee19);
963
+ }, _callee21);
835
964
  })));
836
- it('should optimistically dispatch a state update when switching to two-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee20() {
837
- return _regeneratorRuntime.wrap(function _callee20$(_context20) {
838
- while (1) switch (_context20.prev = _context20.next) {
965
+ it('should optimistically dispatch a state update when switching to two-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee22() {
966
+ return _regeneratorRuntime.wrap(function _callee22$(_context22) {
967
+ while (1) switch (_context22.prev = _context22.next) {
839
968
  case 0:
840
- _context20.next = 2;
969
+ _context22.next = 2;
841
970
  return instance.updateSharingMemberType('sharing-123', 1, 'two-way');
842
971
 
843
972
  case 2:
@@ -854,44 +983,44 @@ describe('updateSharingMemberType', function () {
854
983
 
855
984
  case 3:
856
985
  case "end":
857
- return _context20.stop();
986
+ return _context22.stop();
858
987
  }
859
- }, _callee20);
988
+ }, _callee22);
860
989
  })));
861
- it('should rethrow error if setReadOnly fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee21() {
862
- return _regeneratorRuntime.wrap(function _callee21$(_context21) {
863
- while (1) switch (_context21.prev = _context21.next) {
990
+ it('should rethrow error if setReadOnly fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee23() {
991
+ return _regeneratorRuntime.wrap(function _callee23$(_context23) {
992
+ while (1) switch (_context23.prev = _context23.next) {
864
993
  case 0:
865
994
  instance.sharingCol.setReadOnly.mockRejectedValue(new Error('Network error'));
866
- _context21.next = 3;
995
+ _context23.next = 3;
867
996
  return expect(instance.updateSharingMemberType('sharing-123', 1, 'one-way')).rejects.toThrow('Network error');
868
997
 
869
998
  case 3:
870
999
  case "end":
871
- return _context21.stop();
1000
+ return _context23.stop();
872
1001
  }
873
- }, _callee21);
1002
+ }, _callee23);
874
1003
  })));
875
- it('should rethrow error if setReadWrite fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee22() {
876
- return _regeneratorRuntime.wrap(function _callee22$(_context22) {
877
- while (1) switch (_context22.prev = _context22.next) {
1004
+ it('should rethrow error if setReadWrite fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee24() {
1005
+ return _regeneratorRuntime.wrap(function _callee24$(_context24) {
1006
+ while (1) switch (_context24.prev = _context24.next) {
878
1007
  case 0:
879
1008
  instance.sharingCol.setReadWrite.mockRejectedValue(new Error('Network error'));
880
- _context22.next = 3;
1009
+ _context24.next = 3;
881
1010
  return expect(instance.updateSharingMemberType('sharing-123', 1, 'two-way')).rejects.toThrow('Network error');
882
1011
 
883
1012
  case 3:
884
1013
  case "end":
885
- return _context22.stop();
1014
+ return _context24.stop();
886
1015
  }
887
- }, _callee22);
1016
+ }, _callee24);
888
1017
  })));
889
- it('should rollback optimistic state update if API call fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee23() {
890
- return _regeneratorRuntime.wrap(function _callee23$(_context23) {
891
- while (1) switch (_context23.prev = _context23.next) {
1018
+ it('should rollback optimistic state update if API call fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee25() {
1019
+ return _regeneratorRuntime.wrap(function _callee25$(_context25) {
1020
+ while (1) switch (_context25.prev = _context25.next) {
892
1021
  case 0:
893
1022
  instance.sharingCol.setReadOnly.mockRejectedValue(new Error('Network error'));
894
- _context23.next = 3;
1023
+ _context25.next = 3;
895
1024
  return expect(instance.updateSharingMemberType('sharing-123', 1, 'one-way')).rejects.toThrow('Network error');
896
1025
 
897
1026
  case 3:
@@ -918,14 +1047,14 @@ describe('updateSharingMemberType', function () {
918
1047
 
919
1048
  case 5:
920
1049
  case "end":
921
- return _context23.stop();
1050
+ return _context25.stop();
922
1051
  }
923
- }, _callee23);
1052
+ }, _callee25);
924
1053
  })));
925
- it('should preserve realtime changes when rolling back an API failure', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee25() {
1054
+ it('should preserve realtime changes when rolling back an API failure', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee27() {
926
1055
  var realtimeSharing;
927
- return _regeneratorRuntime.wrap(function _callee25$(_context25) {
928
- while (1) switch (_context25.prev = _context25.next) {
1056
+ return _regeneratorRuntime.wrap(function _callee27$(_context27) {
1057
+ while (1) switch (_context27.prev = _context27.next) {
929
1058
  case 0:
930
1059
  realtimeSharing = _objectSpread(_objectSpread({}, mockSharing), {}, {
931
1060
  attributes: _objectSpread(_objectSpread({}, mockSharing.attributes), {}, {
@@ -937,9 +1066,9 @@ describe('updateSharingMemberType', function () {
937
1066
  })]
938
1067
  })
939
1068
  });
940
- instance.sharingCol.setReadOnly.mockImplementation( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee24() {
941
- return _regeneratorRuntime.wrap(function _callee24$(_context24) {
942
- while (1) switch (_context24.prev = _context24.next) {
1069
+ instance.sharingCol.setReadOnly.mockImplementation( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee26() {
1070
+ return _regeneratorRuntime.wrap(function _callee26$(_context26) {
1071
+ while (1) switch (_context26.prev = _context26.next) {
943
1072
  case 0:
944
1073
  instance.state = _objectSpread(_objectSpread({}, instance.state), {}, {
945
1074
  sharings: instance.state.sharings.map(function (sharing) {
@@ -950,11 +1079,11 @@ describe('updateSharingMemberType', function () {
950
1079
 
951
1080
  case 2:
952
1081
  case "end":
953
- return _context24.stop();
1082
+ return _context26.stop();
954
1083
  }
955
- }, _callee24);
1084
+ }, _callee26);
956
1085
  })));
957
- _context25.next = 4;
1086
+ _context27.next = 4;
958
1087
  return expect(instance.updateSharingMemberType('sharing-123', 1, 'one-way')).rejects.toThrow('Network error');
959
1088
 
960
1089
  case 4:
@@ -971,9 +1100,9 @@ describe('updateSharingMemberType', function () {
971
1100
 
972
1101
  case 5:
973
1102
  case "end":
974
- return _context25.stop();
1103
+ return _context27.stop();
975
1104
  }
976
- }, _callee25);
1105
+ }, _callee27);
977
1106
  })));
978
1107
  }); // TODO Convert with react-testing-library
979
1108
  // describe('hasWriteAccess', () => {
package/dist/state.js CHANGED
@@ -237,7 +237,7 @@ var byDocId = function byDocId() {
237
237
  case REVOKE_GROUP:
238
238
  case REVOKE_RECIPIENT:
239
239
  case UPDATE_SHARING:
240
- if (!isSharingADrive(action.sharing) && areAllRecipientsRevoked(action.sharing)) {
240
+ if (areAllRecipientsRevoked(action.sharing)) {
241
241
  return forgetSharing(state, action.sharing);
242
242
  }
243
243
 
@@ -337,7 +337,7 @@ var sharings = function sharings() {
337
337
  case UPDATE_SHARING:
338
338
  case REVOKE_GROUP:
339
339
  case REVOKE_RECIPIENT:
340
- if (!isSharingADrive(action.sharing) && areAllRecipientsRevoked(action.sharing)) {
340
+ if (areAllRecipientsRevoked(action.sharing)) {
341
341
  return state.filter(function (s) {
342
342
  return s.id !== action.sharing.id;
343
343
  });
@@ -374,7 +374,8 @@ var sharedPaths = function sharedPaths() {
374
374
 
375
375
  case REVOKE_GROUP:
376
376
  case REVOKE_RECIPIENT:
377
- if (!isSharingADrive(action.sharing) && areAllRecipientsRevoked(action.sharing)) {
377
+ case UPDATE_SHARING:
378
+ if (areAllRecipientsRevoked(action.sharing)) {
378
379
  return state.filter(function (p) {
379
380
  return p !== action.path;
380
381
  });
@@ -4,7 +4,7 @@ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (O
4
4
 
5
5
  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; }
6
6
 
7
- import reducer, { receiveSharings, addSharing, addSharingLink, updateSharingLink, revokeSharingLink, getRecipients, getRecipientsFromSharing, revokeRecipient, revokeSelf, updateSharing, matchingInstanceName, getSharingLink, hasSharedParent, hasSharedChild, getSharedDocIdsBySharings, getSharingType, getPermissionDocIds, getDocumentSharingType, getSharedParentPath, getExternalSharingIds, getRecipientsWithGroups } from './state';
7
+ import reducer, { receiveSharings, addSharing, addSharingLink, updateSharingLink, revokeSharingLink, getRecipients, getRecipientsFromSharing, revokeGroup, revokeRecipient, revokeSelf, updateSharing, matchingInstanceName, getSharingLink, hasSharedParent, hasSharedChild, getSharedDocIdsBySharings, getSharingType, getPermissionDocIds, getDocumentSharingType, getSharedParentPath, getExternalSharingIds, getRecipientsWithGroups } from './state';
8
8
  import { SHARING_1, SHARING_2, SHARING_3, SHARING_READ_ONLY, PERM_1, PERM_2, PERM_WITHOUT_DOC, SHARING_NO_ACTIVE, SHARING_WITH_SHORTCUT, APPS } from '../__tests__/fixtures';
9
9
  describe('Sharing state', function () {
10
10
  it('should have a default state', function () {
@@ -106,7 +106,7 @@ describe('Sharing state', function () {
106
106
  });
107
107
  expect(state.sharings).toEqual([SHARING_1, SHARED_DRIVE_WITHOUT_RECIPIENTS]);
108
108
  });
109
- it('should not forget shared drives without recipients when updating', function () {
109
+ it('should forget shared drives without recipients when updating', function () {
110
110
  var SHARED_DRIVE_WITHOUT_RECIPIENTS = _objectSpread(_objectSpread({}, SHARING_1), {}, {
111
111
  id: 'shared_drive_no_recipients',
112
112
  attributes: _objectSpread(_objectSpread({}, SHARING_1.attributes), {}, {
@@ -139,15 +139,45 @@ describe('Sharing state', function () {
139
139
  });
140
140
 
141
141
  var state = reducer(initialState, updateSharing(updatedSharing));
142
- expect(state.byDocId).toEqual({
143
- shared_drive_folder: {
144
- sharings: [SHARED_DRIVE_WITHOUT_RECIPIENTS.id],
145
- permissions: []
146
- }
147
- });
148
- expect(state.sharings).toEqual([updatedSharing]);
142
+ expect(state.byDocId).toEqual({});
143
+ expect(state.sharings).toEqual([]);
149
144
  });
150
- it('should not remove shared drive path when revoking last recipient', function () {
145
+ it('should forget a shared drive when revoking its last group', function () {
146
+ var SHARED_DRIVE_WITH_GROUP = _objectSpread(_objectSpread({}, SHARING_1), {}, {
147
+ id: 'shared_drive_group',
148
+ attributes: _objectSpread(_objectSpread({}, SHARING_1.attributes), {}, {
149
+ drive: true,
150
+ members: [{
151
+ status: 'owner',
152
+ name: 'Jane Doe',
153
+ email: 'jane@doe.com',
154
+ instance: 'http://cozy.tools:8080'
155
+ }],
156
+ groups: [{
157
+ name: 'Last group',
158
+ addedBy: 0
159
+ }],
160
+ rules: [{
161
+ title: 'My Shared Drive',
162
+ doctype: 'io.cozy.files',
163
+ values: ['shared_drive_group_folder'],
164
+ add: 'sync',
165
+ update: 'sync',
166
+ remove: 'sync'
167
+ }]
168
+ })
169
+ });
170
+
171
+ var sharedDrivePath = '/shared_drive_group_folder';
172
+ var initialState = reducer(reducer(undefined, receiveSharings({
173
+ sharings: [SHARED_DRIVE_WITH_GROUP]
174
+ })), addSharing(SHARED_DRIVE_WITH_GROUP, sharedDrivePath));
175
+ var state = reducer(initialState, revokeGroup(SHARED_DRIVE_WITH_GROUP, 0, sharedDrivePath));
176
+ expect(state.byDocId).toEqual({});
177
+ expect(state.sharings).toEqual([]);
178
+ expect(state.sharedPaths).toEqual([]);
179
+ });
180
+ it('should forget a shared drive when revoking last recipient', function () {
151
181
  var SHARED_DRIVE_WITH_ONE_RECIPIENT = _objectSpread(_objectSpread({}, SHARING_1), {}, {
152
182
  id: 'shared_drive_one_recipient',
153
183
  attributes: _objectSpread(_objectSpread({}, SHARING_1.attributes), {}, {
@@ -178,23 +208,10 @@ describe('Sharing state', function () {
178
208
  var initialState = reducer(reducer(undefined, receiveSharings({
179
209
  sharings: [SHARED_DRIVE_WITH_ONE_RECIPIENT]
180
210
  })), addSharing(SHARED_DRIVE_WITH_ONE_RECIPIENT, sharedDrivePath));
181
-
182
- var sharingAfterRevoke = _objectSpread(_objectSpread({}, SHARED_DRIVE_WITH_ONE_RECIPIENT), {}, {
183
- attributes: _objectSpread(_objectSpread({}, SHARED_DRIVE_WITH_ONE_RECIPIENT.attributes), {}, {
184
- members: [SHARED_DRIVE_WITH_ONE_RECIPIENT.attributes.members[0], _objectSpread(_objectSpread({}, SHARED_DRIVE_WITH_ONE_RECIPIENT.attributes.members[1]), {}, {
185
- status: 'revoked'
186
- })]
187
- })
188
- });
189
-
190
- var state = reducer(initialState, revokeRecipient(sharingAfterRevoke, 1, sharedDrivePath));
191
- expect(state.sharedPaths).toContain(sharedDrivePath);
192
- expect(state.byDocId).toEqual({
193
- shared_drive_folder: {
194
- sharings: [SHARED_DRIVE_WITH_ONE_RECIPIENT.id],
195
- permissions: []
196
- }
197
- });
211
+ var state = reducer(initialState, revokeRecipient(SHARED_DRIVE_WITH_ONE_RECIPIENT, 1, sharedDrivePath));
212
+ expect(state.byDocId).toEqual({});
213
+ expect(state.sharings).toEqual([]);
214
+ expect(state.sharedPaths).toEqual([]);
198
215
  });
199
216
  it('should index received permissions', function () {
200
217
  var state = reducer(undefined, receiveSharings({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-sharing",
3
- "version": "33.6.0",
3
+ "version": "33.6.1",
4
4
  "description": "Provides sharing login for React applications.",
5
5
  "main": "dist/index.js",
6
6
  "author": "Cozy",
@@ -83,5 +83,5 @@
83
83
  "sideEffects": [
84
84
  "*.css"
85
85
  ],
86
- "gitHead": "fc451fb2e649fffe6bfc8f16ff812ba4c31531cf"
86
+ "gitHead": "10aa2298384087fd396bbfce29c1198d80244361"
87
87
  }
@@ -5,7 +5,12 @@ import { createMockClient } from 'cozy-client'
5
5
 
6
6
  import { SharingProvider } from './SharingProvider'
7
7
  import SharingContext from './context'
8
- import reducer, { addSharingLink, getDocumentPermissions } from './state'
8
+ import reducer, {
9
+ addSharingLink,
10
+ getDocumentPermissions,
11
+ getDocumentSharing,
12
+ receiveSharings
13
+ } from './state'
9
14
  import AppLike from '../test/AppLike'
10
15
 
11
16
  const AppWrapper = ({ children, client, isPublic }) => {
@@ -291,6 +296,115 @@ describe('updateDocumentPermissions', () => {
291
296
  })
292
297
  })
293
298
 
299
+ describe('shared drive recipient revocation', () => {
300
+ const SHARED_DRIVE = {
301
+ _id: 'sharing_123',
302
+ id: 'sharing_123',
303
+ type: 'io.cozy.sharings',
304
+ attributes: {
305
+ drive: true,
306
+ owner: true,
307
+ description: 'Shared folder',
308
+ rules: [
309
+ {
310
+ title: 'Shared folder',
311
+ doctype: 'io.cozy.files',
312
+ values: ['folder_123'],
313
+ add: 'none',
314
+ update: 'none',
315
+ remove: 'none'
316
+ }
317
+ ],
318
+ members: [
319
+ {
320
+ status: 'owner',
321
+ email: 'owner@cozy.local',
322
+ instance: 'http://cozy.local'
323
+ },
324
+ {
325
+ status: 'pending',
326
+ email: 'recipient@cozy.local',
327
+ instance: 'http://recipient.cozy.local'
328
+ }
329
+ ]
330
+ }
331
+ }
332
+ const NEW_SHARING = {
333
+ ...SHARED_DRIVE,
334
+ _id: 'sharing_456',
335
+ id: 'sharing_456'
336
+ }
337
+ const document = {
338
+ _id: 'folder_123',
339
+ id: 'folder_123',
340
+ path: '/Shared folder'
341
+ }
342
+ const recipient = {
343
+ _id: 'contact_123',
344
+ id: 'contact_123',
345
+ _type: 'io.cozy.contacts',
346
+ email: 'recipient@cozy.local'
347
+ }
348
+
349
+ let provider
350
+ let sharingCol
351
+
352
+ beforeEach(() => {
353
+ const mockClient = createMockClient({})
354
+ mockClient.getStackClient = () => ({ uri: 'http://cozy.local' })
355
+ mockClient.collection = jest.fn().mockReturnValue({})
356
+
357
+ provider = new SharingProvider({ client: mockClient })
358
+ provider.state = {
359
+ ...provider.state,
360
+ ...reducer(undefined, receiveSharings({ sharings: [SHARED_DRIVE] }))
361
+ }
362
+ provider.dispatch = jest.fn(action => {
363
+ provider.state = {
364
+ ...provider.state,
365
+ ...reducer(provider.state, action)
366
+ }
367
+ })
368
+ sharingCol = {
369
+ addRecipients: jest.fn(),
370
+ create: jest.fn().mockResolvedValue({ data: NEW_SHARING }),
371
+ revokeRecipient: jest.fn().mockResolvedValue({})
372
+ }
373
+ provider.sharingCol = sharingCol
374
+ })
375
+
376
+ it('forgets the shared drive when revoking its last recipient', async () => {
377
+ await provider.revoke(document, SHARED_DRIVE.id, 1)
378
+
379
+ expect(sharingCol.revokeRecipient).toHaveBeenCalledWith(SHARED_DRIVE, 1)
380
+ expect(getDocumentSharing(provider.state, document.id)).toBeNull()
381
+ })
382
+
383
+ it('creates a new sharing when re-adding after the last recipient was revoked', async () => {
384
+ await provider.revoke(document, SHARED_DRIVE.id, 1)
385
+
386
+ await provider.share({
387
+ document,
388
+ recipients: [recipient],
389
+ readOnlyRecipients: [],
390
+ description: 'Shared folder',
391
+ openSharing: true,
392
+ sharedDrive: true
393
+ })
394
+
395
+ expect(sharingCol.addRecipients).not.toHaveBeenCalled()
396
+ expect(sharingCol.create).toHaveBeenCalledWith({
397
+ document,
398
+ recipients: [recipient],
399
+ readOnlyRecipients: [],
400
+ description: 'Shared folder',
401
+ previewPath: '/preview',
402
+ openSharing: true,
403
+ sharedDrive: true
404
+ })
405
+ })
406
+ })
407
+
294
408
  describe('fetchSharedDriveSharingLinks', () => {
295
409
  const PERM_DRIVE_FILE = {
296
410
  type: 'io.cozy.permissions',
package/src/state.js CHANGED
@@ -184,10 +184,7 @@ const byDocId = (state = {}, action) => {
184
184
  case REVOKE_GROUP:
185
185
  case REVOKE_RECIPIENT:
186
186
  case UPDATE_SHARING:
187
- if (
188
- !isSharingADrive(action.sharing) &&
189
- areAllRecipientsRevoked(action.sharing)
190
- ) {
187
+ if (areAllRecipientsRevoked(action.sharing)) {
191
188
  return forgetSharing(state, action.sharing)
192
189
  }
193
190
  return state
@@ -258,10 +255,7 @@ const sharings = (state = [], action) => {
258
255
  case UPDATE_SHARING:
259
256
  case REVOKE_GROUP:
260
257
  case REVOKE_RECIPIENT:
261
- if (
262
- !isSharingADrive(action.sharing) &&
263
- areAllRecipientsRevoked(action.sharing)
264
- ) {
258
+ if (areAllRecipientsRevoked(action.sharing)) {
265
259
  return state.filter(s => s.id !== action.sharing.id)
266
260
  }
267
261
  return state.map(s => {
@@ -287,10 +281,8 @@ const sharedPaths = (state = [], action) => {
287
281
  return newState
288
282
  case REVOKE_GROUP:
289
283
  case REVOKE_RECIPIENT:
290
- if (
291
- !isSharingADrive(action.sharing) &&
292
- areAllRecipientsRevoked(action.sharing)
293
- ) {
284
+ case UPDATE_SHARING:
285
+ if (areAllRecipientsRevoked(action.sharing)) {
294
286
  return state.filter(p => p !== action.path)
295
287
  }
296
288
  return state
package/src/state.spec.js CHANGED
@@ -6,6 +6,7 @@ import reducer, {
6
6
  revokeSharingLink,
7
7
  getRecipients,
8
8
  getRecipientsFromSharing,
9
+ revokeGroup,
9
10
  revokeRecipient,
10
11
  revokeSelf,
11
12
  updateSharing,
@@ -143,7 +144,7 @@ describe('Sharing state', () => {
143
144
  expect(state.sharings).toEqual([SHARING_1, SHARED_DRIVE_WITHOUT_RECIPIENTS])
144
145
  })
145
146
 
146
- it('should not forget shared drives without recipients when updating', () => {
147
+ it('should forget shared drives without recipients when updating', () => {
147
148
  const SHARED_DRIVE_WITHOUT_RECIPIENTS = {
148
149
  ...SHARING_1,
149
150
  id: 'shared_drive_no_recipients',
@@ -184,16 +185,65 @@ describe('Sharing state', () => {
184
185
  }
185
186
  }
186
187
  const state = reducer(initialState, updateSharing(updatedSharing))
187
- expect(state.byDocId).toEqual({
188
- shared_drive_folder: {
189
- sharings: [SHARED_DRIVE_WITHOUT_RECIPIENTS.id],
190
- permissions: []
188
+
189
+ expect(state.byDocId).toEqual({})
190
+ expect(state.sharings).toEqual([])
191
+ })
192
+
193
+ it('should forget a shared drive when revoking its last group', () => {
194
+ const SHARED_DRIVE_WITH_GROUP = {
195
+ ...SHARING_1,
196
+ id: 'shared_drive_group',
197
+ attributes: {
198
+ ...SHARING_1.attributes,
199
+ drive: true,
200
+ members: [
201
+ {
202
+ status: 'owner',
203
+ name: 'Jane Doe',
204
+ email: 'jane@doe.com',
205
+ instance: 'http://cozy.tools:8080'
206
+ }
207
+ ],
208
+ groups: [
209
+ {
210
+ name: 'Last group',
211
+ addedBy: 0
212
+ }
213
+ ],
214
+ rules: [
215
+ {
216
+ title: 'My Shared Drive',
217
+ doctype: 'io.cozy.files',
218
+ values: ['shared_drive_group_folder'],
219
+ add: 'sync',
220
+ update: 'sync',
221
+ remove: 'sync'
222
+ }
223
+ ]
191
224
  }
192
- })
193
- expect(state.sharings).toEqual([updatedSharing])
225
+ }
226
+ const sharedDrivePath = '/shared_drive_group_folder'
227
+ const initialState = reducer(
228
+ reducer(
229
+ undefined,
230
+ receiveSharings({
231
+ sharings: [SHARED_DRIVE_WITH_GROUP]
232
+ })
233
+ ),
234
+ addSharing(SHARED_DRIVE_WITH_GROUP, sharedDrivePath)
235
+ )
236
+ const state = reducer(
237
+ initialState,
238
+ revokeGroup(SHARED_DRIVE_WITH_GROUP, 0, sharedDrivePath)
239
+ )
240
+
241
+ expect(state.byDocId).toEqual({})
242
+ expect(state.sharings).toEqual([])
243
+ expect(state.sharedPaths).toEqual([])
194
244
  })
195
245
 
196
- it('should not remove shared drive path when revoking last recipient', () => {
246
+ it('should forget a shared drive when revoking last recipient', () => {
197
247
  const SHARED_DRIVE_WITH_ONE_RECIPIENT = {
198
248
  ...SHARING_1,
199
249
  id: 'shared_drive_one_recipient',
@@ -236,30 +286,14 @@ describe('Sharing state', () => {
236
286
  ),
237
287
  addSharing(SHARED_DRIVE_WITH_ONE_RECIPIENT, sharedDrivePath)
238
288
  )
239
- const sharingAfterRevoke = {
240
- ...SHARED_DRIVE_WITH_ONE_RECIPIENT,
241
- attributes: {
242
- ...SHARED_DRIVE_WITH_ONE_RECIPIENT.attributes,
243
- members: [
244
- SHARED_DRIVE_WITH_ONE_RECIPIENT.attributes.members[0],
245
- {
246
- ...SHARED_DRIVE_WITH_ONE_RECIPIENT.attributes.members[1],
247
- status: 'revoked'
248
- }
249
- ]
250
- }
251
- }
252
289
  const state = reducer(
253
290
  initialState,
254
- revokeRecipient(sharingAfterRevoke, 1, sharedDrivePath)
291
+ revokeRecipient(SHARED_DRIVE_WITH_ONE_RECIPIENT, 1, sharedDrivePath)
255
292
  )
256
- expect(state.sharedPaths).toContain(sharedDrivePath)
257
- expect(state.byDocId).toEqual({
258
- shared_drive_folder: {
259
- sharings: [SHARED_DRIVE_WITH_ONE_RECIPIENT.id],
260
- permissions: []
261
- }
262
- })
293
+
294
+ expect(state.byDocId).toEqual({})
295
+ expect(state.sharings).toEqual([])
296
+ expect(state.sharedPaths).toEqual([])
263
297
  })
264
298
 
265
299
  it('should index received permissions', () => {