cozy-sharing 33.4.1 → 33.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,19 @@
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.4.3](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.4.2...cozy-sharing@33.4.3) (2026-06-16)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **cozy-sharing:** Remove revoked sharing after last recipient ([e0f486c](https://github.com/cozy/cozy-libs/commit/e0f486cd9b7805e8214c5586f0d7b2c7aed50491))
11
+
12
+ ## [33.4.2](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.4.1...cozy-sharing@33.4.2) (2026-06-16)
13
+
14
+ ### Bug Fixes
15
+
16
+ - **cozy-sharing:** Preserve sharing updates on rollback ([7c6db1a](https://github.com/cozy/cozy-libs/commit/7c6db1ad63f3997f01bdfee7140f9dad36dde2c2))
17
+ - **sharing:** Update member permissions optimistically ([5c1ac86](https://github.com/cozy/cozy-libs/commit/5c1ac8631e4598ac6d2bc08761d2064c9b396f66))
18
+
6
19
  ## [33.4.1](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.4.0...cozy-sharing@33.4.1) (2026-06-15)
7
20
 
8
21
  ### Bug Fixes
@@ -471,7 +471,7 @@ export var SharingProvider = /*#__PURE__*/function (_Component) {
471
471
  */
472
472
  _defineProperty(_this2, "updateSharingMemberType", /*#__PURE__*/function () {
473
473
  var _ref12 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee10(sharingId, memberIndex, newType) {
474
- var sharing, member, makeReadOnly;
474
+ var sharing, member, makeReadOnly, optimisticSharing, currentSharing, rollbackSharing;
475
475
  return _regeneratorRuntime.wrap(function _callee10$(_context10) {
476
476
  while (1) switch (_context10.prev = _context10.next) {
477
477
  case 0:
@@ -505,39 +505,64 @@ export var SharingProvider = /*#__PURE__*/function (_Component) {
505
505
  throw new Error('Unsupported sharing type: ' + newType);
506
506
 
507
507
  case 9:
508
- _context10.prev = 9;
508
+ optimisticSharing = _objectSpread(_objectSpread({}, sharing), {}, {
509
+ attributes: _objectSpread(_objectSpread({}, sharing.attributes), {}, {
510
+ members: sharing.attributes.members.map(function (member, index) {
511
+ return index === memberIndex ? _objectSpread(_objectSpread({}, member), {}, {
512
+ read_only: makeReadOnly
513
+ }) : member;
514
+ })
515
+ })
516
+ });
517
+
518
+ _this2.dispatch(updateSharing(optimisticSharing));
519
+
520
+ _context10.prev = 11;
509
521
 
510
522
  if (!makeReadOnly) {
511
- _context10.next = 15;
523
+ _context10.next = 17;
512
524
  break;
513
525
  }
514
526
 
515
- _context10.next = 13;
527
+ _context10.next = 15;
516
528
  return _this2.sharingCol.setReadOnly(sharing, memberIndex);
517
529
 
518
- case 13:
519
- _context10.next = 17;
530
+ case 15:
531
+ _context10.next = 19;
520
532
  break;
521
533
 
522
- case 15:
523
- _context10.next = 17;
534
+ case 17:
535
+ _context10.next = 19;
524
536
  return _this2.sharingCol.setReadWrite(sharing, memberIndex);
525
537
 
526
- case 17:
527
- _context10.next = 23;
538
+ case 19:
539
+ _context10.next = 28;
528
540
  break;
529
541
 
530
- case 19:
531
- _context10.prev = 19;
532
- _context10.t0 = _context10["catch"](9);
542
+ case 21:
543
+ _context10.prev = 21;
544
+ _context10.t0 = _context10["catch"](11);
545
+ currentSharing = _getSharingById(_this2.state, sharingId) || sharing;
546
+ rollbackSharing = _objectSpread(_objectSpread({}, currentSharing), {}, {
547
+ attributes: _objectSpread(_objectSpread({}, currentSharing.attributes), {}, {
548
+ members: currentSharing.attributes.members.map(function (member, index) {
549
+ return index === memberIndex ? _objectSpread(_objectSpread({}, member), {}, {
550
+ read_only: !makeReadOnly
551
+ }) : member;
552
+ })
553
+ })
554
+ });
555
+
556
+ _this2.dispatch(updateSharing(rollbackSharing));
557
+
533
558
  log.error("Failed to change member ".concat(member.email, " permission type to ").concat(newType), _context10.t0);
534
559
  throw _context10.t0;
535
560
 
536
- case 23:
561
+ case 28:
537
562
  case "end":
538
563
  return _context10.stop();
539
564
  }
540
- }, _callee10, null, [[9, 19]]);
565
+ }, _callee10, null, [[11, 21]]);
541
566
  }));
542
567
 
543
568
  return function (_x16, _x17, _x18) {
@@ -744,7 +744,9 @@ describe('updateSharingMemberType', function () {
744
744
  setReadOnly: jest.fn().mockResolvedValue({}),
745
745
  setReadWrite: jest.fn().mockResolvedValue({})
746
746
  };
747
- jest.spyOn(instance, 'setState');
747
+ instance.dispatch = jest.fn(function (action) {
748
+ instance.state = _objectSpread(_objectSpread({}, instance.state), reducer(instance.state, action));
749
+ });
748
750
  });
749
751
  it('should throw when sharing is not found', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee15() {
750
752
  return _regeneratorRuntime.wrap(function _callee15$(_context15) {
@@ -806,7 +808,7 @@ describe('updateSharingMemberType', function () {
806
808
  }
807
809
  }, _callee18);
808
810
  })));
809
- it('should not dispatch a state update realtime refreshes the store', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee19() {
811
+ it('should optimistically dispatch a state update when switching to one-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee19() {
810
812
  return _regeneratorRuntime.wrap(function _callee19$(_context19) {
811
813
  while (1) switch (_context19.prev = _context19.next) {
812
814
  case 0:
@@ -814,7 +816,16 @@ describe('updateSharingMemberType', function () {
814
816
  return instance.updateSharingMemberType('sharing-123', 1, 'one-way');
815
817
 
816
818
  case 2:
817
- expect(instance.setState).not.toHaveBeenCalled();
819
+ expect(instance.dispatch).toHaveBeenCalledWith({
820
+ type: 'UPDATE_SHARING',
821
+ sharing: _objectSpread(_objectSpread({}, mockSharing), {}, {
822
+ attributes: _objectSpread(_objectSpread({}, mockSharing.attributes), {}, {
823
+ members: [mockSharing.attributes.members[0], _objectSpread(_objectSpread({}, mockSharing.attributes.members[1]), {}, {
824
+ read_only: true
825
+ })]
826
+ })
827
+ })
828
+ });
818
829
 
819
830
  case 3:
820
831
  case "end":
@@ -822,7 +833,7 @@ describe('updateSharingMemberType', function () {
822
833
  }
823
834
  }, _callee19);
824
835
  })));
825
- it('should not dispatch a state update for two-way — realtime refreshes the store', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee20() {
836
+ it('should optimistically dispatch a state update when switching to two-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee20() {
826
837
  return _regeneratorRuntime.wrap(function _callee20$(_context20) {
827
838
  while (1) switch (_context20.prev = _context20.next) {
828
839
  case 0:
@@ -830,7 +841,16 @@ describe('updateSharingMemberType', function () {
830
841
  return instance.updateSharingMemberType('sharing-123', 1, 'two-way');
831
842
 
832
843
  case 2:
833
- expect(instance.setState).not.toHaveBeenCalled();
844
+ expect(instance.dispatch).toHaveBeenCalledWith({
845
+ type: 'UPDATE_SHARING',
846
+ sharing: _objectSpread(_objectSpread({}, mockSharing), {}, {
847
+ attributes: _objectSpread(_objectSpread({}, mockSharing.attributes), {}, {
848
+ members: [mockSharing.attributes.members[0], _objectSpread(_objectSpread({}, mockSharing.attributes.members[1]), {}, {
849
+ read_only: false
850
+ })]
851
+ })
852
+ })
853
+ });
834
854
 
835
855
  case 3:
836
856
  case "end":
@@ -866,7 +886,7 @@ describe('updateSharingMemberType', function () {
866
886
  }
867
887
  }, _callee22);
868
888
  })));
869
- it('should not dispatch state update if API call fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee23() {
889
+ it('should rollback optimistic state update if API call fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee23() {
870
890
  return _regeneratorRuntime.wrap(function _callee23$(_context23) {
871
891
  while (1) switch (_context23.prev = _context23.next) {
872
892
  case 0:
@@ -875,14 +895,86 @@ describe('updateSharingMemberType', function () {
875
895
  return expect(instance.updateSharingMemberType('sharing-123', 1, 'one-way')).rejects.toThrow('Network error');
876
896
 
877
897
  case 3:
878
- expect(instance.setState).not.toHaveBeenCalled();
898
+ expect(instance.dispatch).toHaveBeenNthCalledWith(1, {
899
+ type: 'UPDATE_SHARING',
900
+ sharing: _objectSpread(_objectSpread({}, mockSharing), {}, {
901
+ attributes: _objectSpread(_objectSpread({}, mockSharing.attributes), {}, {
902
+ members: [mockSharing.attributes.members[0], _objectSpread(_objectSpread({}, mockSharing.attributes.members[1]), {}, {
903
+ read_only: true
904
+ })]
905
+ })
906
+ })
907
+ });
908
+ expect(instance.dispatch).toHaveBeenNthCalledWith(2, {
909
+ type: 'UPDATE_SHARING',
910
+ sharing: _objectSpread(_objectSpread({}, mockSharing), {}, {
911
+ attributes: _objectSpread(_objectSpread({}, mockSharing.attributes), {}, {
912
+ members: [mockSharing.attributes.members[0], _objectSpread(_objectSpread({}, mockSharing.attributes.members[1]), {}, {
913
+ read_only: false
914
+ })]
915
+ })
916
+ })
917
+ });
879
918
 
880
- case 4:
919
+ case 5:
881
920
  case "end":
882
921
  return _context23.stop();
883
922
  }
884
923
  }, _callee23);
885
924
  })));
925
+ it('should preserve realtime changes when rolling back an API failure', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee25() {
926
+ var realtimeSharing;
927
+ return _regeneratorRuntime.wrap(function _callee25$(_context25) {
928
+ while (1) switch (_context25.prev = _context25.next) {
929
+ case 0:
930
+ realtimeSharing = _objectSpread(_objectSpread({}, mockSharing), {}, {
931
+ attributes: _objectSpread(_objectSpread({}, mockSharing.attributes), {}, {
932
+ description: 'updated by realtime',
933
+ members: [_objectSpread(_objectSpread({}, mockSharing.attributes.members[0]), {}, {
934
+ status: 'seen'
935
+ }), _objectSpread(_objectSpread({}, mockSharing.attributes.members[1]), {}, {
936
+ read_only: true
937
+ })]
938
+ })
939
+ });
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) {
943
+ case 0:
944
+ instance.state = _objectSpread(_objectSpread({}, instance.state), {}, {
945
+ sharings: instance.state.sharings.map(function (sharing) {
946
+ return sharing.id === realtimeSharing.id ? realtimeSharing : sharing;
947
+ })
948
+ });
949
+ throw new Error('Network error');
950
+
951
+ case 2:
952
+ case "end":
953
+ return _context24.stop();
954
+ }
955
+ }, _callee24);
956
+ })));
957
+ _context25.next = 4;
958
+ return expect(instance.updateSharingMemberType('sharing-123', 1, 'one-way')).rejects.toThrow('Network error');
959
+
960
+ case 4:
961
+ expect(instance.dispatch).toHaveBeenNthCalledWith(2, {
962
+ type: 'UPDATE_SHARING',
963
+ sharing: _objectSpread(_objectSpread({}, realtimeSharing), {}, {
964
+ attributes: _objectSpread(_objectSpread({}, realtimeSharing.attributes), {}, {
965
+ members: [realtimeSharing.attributes.members[0], _objectSpread(_objectSpread({}, realtimeSharing.attributes.members[1]), {}, {
966
+ read_only: false
967
+ })]
968
+ })
969
+ })
970
+ });
971
+
972
+ case 5:
973
+ case "end":
974
+ return _context25.stop();
975
+ }
976
+ }, _callee25);
977
+ })));
886
978
  }); // TODO Convert with react-testing-library
887
979
  // describe('hasWriteAccess', () => {
888
980
  // it('tells if a doc is writtable', () => {
@@ -112,17 +112,18 @@ var MemberRecipientPermissions = function MemberRecipientPermissions(_ref) {
112
112
  return _context2.abrupt("return");
113
113
 
114
114
  case 3:
115
- _context2.prev = 3;
116
- _context2.next = 6;
115
+ hideMenu();
116
+ _context2.prev = 4;
117
+ _context2.next = 7;
117
118
  return updateSharingMemberType(sharingId, memberIndex, newType);
118
119
 
119
- case 6:
120
- _context2.next = 12;
120
+ case 7:
121
+ _context2.next = 13;
121
122
  break;
122
123
 
123
- case 8:
124
- _context2.prev = 8;
125
- _context2.t0 = _context2["catch"](3);
124
+ case 9:
125
+ _context2.prev = 9;
126
+ _context2.t0 = _context2["catch"](4);
126
127
  log.error('Failed to change member permission type', _context2.t0);
127
128
  showAlert({
128
129
  message: t('Share.members.error.changePermission'),
@@ -130,14 +131,11 @@ var MemberRecipientPermissions = function MemberRecipientPermissions(_ref) {
130
131
  variant: 'filled'
131
132
  });
132
133
 
133
- case 12:
134
- hideMenu();
135
-
136
134
  case 13:
137
135
  case "end":
138
136
  return _context2.stop();
139
137
  }
140
- }, _callee2, null, [[3, 8]]);
138
+ }, _callee2, null, [[4, 9]]);
141
139
  }));
142
140
 
143
141
  return function (_x) {
package/dist/state.js CHANGED
@@ -337,6 +337,12 @@ 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)) {
341
+ return state.filter(function (s) {
342
+ return s.id !== action.sharing.id;
343
+ });
344
+ }
345
+
340
346
  return state.map(function (s) {
341
347
  return s.id !== action.sharing.id ? s : action.sharing;
342
348
  });
@@ -254,6 +254,18 @@ describe('Sharing state', function () {
254
254
  expect(state.sharings[0].attributes.members[1].status).toEqual('revoked');
255
255
  expect(state.sharings[0].attributes.members[2].id).toEqual(SHARING_1.attributes.members[2].id);
256
256
  });
257
+ it('should forget a sharing when revoking the last recipient', function () {
258
+ var state = reducer(reducer(undefined, receiveSharings({
259
+ sharings: [SHARING_1, SHARING_2]
260
+ })), revokeRecipient(SHARING_2, 1));
261
+ expect(state.byDocId).toEqual({
262
+ folder_1: {
263
+ sharings: [SHARING_1.id],
264
+ permissions: []
265
+ }
266
+ });
267
+ expect(state.sharings).toEqual([SHARING_1]);
268
+ });
257
269
  it('should revoke self', function () {
258
270
  var state = reducer(reducer(undefined, receiveSharings({
259
271
  sharings: [SHARING_1, SHARING_2]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-sharing",
3
- "version": "33.4.1",
3
+ "version": "33.4.3",
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": "a2e29d6c728ebf0fdc890666a9ec3505e90551ed"
86
+ "gitHead": "832c42dbcfdc5db673a0b225163b953031def5de"
87
87
  }
@@ -358,6 +358,24 @@ export class SharingProvider extends Component {
358
358
  if (newType !== 'one-way' && newType !== 'two-way') {
359
359
  throw new Error('Unsupported sharing type: ' + newType)
360
360
  }
361
+
362
+ const optimisticSharing = {
363
+ ...sharing,
364
+ attributes: {
365
+ ...sharing.attributes,
366
+ members: sharing.attributes.members.map((member, index) =>
367
+ index === memberIndex
368
+ ? {
369
+ ...member,
370
+ read_only: makeReadOnly
371
+ }
372
+ : member
373
+ )
374
+ }
375
+ }
376
+
377
+ this.dispatch(updateSharing(optimisticSharing))
378
+
361
379
  try {
362
380
  if (makeReadOnly) {
363
381
  await this.sharingCol.setReadOnly(sharing, memberIndex)
@@ -365,6 +383,23 @@ export class SharingProvider extends Component {
365
383
  await this.sharingCol.setReadWrite(sharing, memberIndex)
366
384
  }
367
385
  } catch (error) {
386
+ const currentSharing = getSharingById(this.state, sharingId) || sharing
387
+ const rollbackSharing = {
388
+ ...currentSharing,
389
+ attributes: {
390
+ ...currentSharing.attributes,
391
+ members: currentSharing.attributes.members.map((member, index) =>
392
+ index === memberIndex
393
+ ? {
394
+ ...member,
395
+ read_only: !makeReadOnly
396
+ }
397
+ : member
398
+ )
399
+ }
400
+ }
401
+
402
+ this.dispatch(updateSharing(rollbackSharing))
368
403
  log.error(
369
404
  `Failed to change member ${member.email} permission type to ${newType}`,
370
405
  error
@@ -524,7 +524,9 @@ describe('updateSharingMemberType', () => {
524
524
  setReadOnly: jest.fn().mockResolvedValue({}),
525
525
  setReadWrite: jest.fn().mockResolvedValue({})
526
526
  }
527
- jest.spyOn(instance, 'setState')
527
+ instance.dispatch = jest.fn(action => {
528
+ instance.state = { ...instance.state, ...reducer(instance.state, action) }
529
+ })
528
530
  })
529
531
 
530
532
  it('should throw when sharing is not found', async () => {
@@ -556,16 +558,46 @@ describe('updateSharingMemberType', () => {
556
558
  expect(instance.sharingCol.setReadOnly).not.toHaveBeenCalled()
557
559
  })
558
560
 
559
- it('should not dispatch a state update realtime refreshes the store', async () => {
561
+ it('should optimistically dispatch a state update when switching to one-way', async () => {
560
562
  await instance.updateSharingMemberType('sharing-123', 1, 'one-way')
561
563
 
562
- expect(instance.setState).not.toHaveBeenCalled()
564
+ expect(instance.dispatch).toHaveBeenCalledWith({
565
+ type: 'UPDATE_SHARING',
566
+ sharing: {
567
+ ...mockSharing,
568
+ attributes: {
569
+ ...mockSharing.attributes,
570
+ members: [
571
+ mockSharing.attributes.members[0],
572
+ {
573
+ ...mockSharing.attributes.members[1],
574
+ read_only: true
575
+ }
576
+ ]
577
+ }
578
+ }
579
+ })
563
580
  })
564
581
 
565
- it('should not dispatch a state update for two-way — realtime refreshes the store', async () => {
582
+ it('should optimistically dispatch a state update when switching to two-way', async () => {
566
583
  await instance.updateSharingMemberType('sharing-123', 1, 'two-way')
567
584
 
568
- expect(instance.setState).not.toHaveBeenCalled()
585
+ expect(instance.dispatch).toHaveBeenCalledWith({
586
+ type: 'UPDATE_SHARING',
587
+ sharing: {
588
+ ...mockSharing,
589
+ attributes: {
590
+ ...mockSharing.attributes,
591
+ members: [
592
+ mockSharing.attributes.members[0],
593
+ {
594
+ ...mockSharing.attributes.members[1],
595
+ read_only: false
596
+ }
597
+ ]
598
+ }
599
+ }
600
+ })
569
601
  })
570
602
 
571
603
  it('should rethrow error if setReadOnly fails', async () => {
@@ -588,7 +620,7 @@ describe('updateSharingMemberType', () => {
588
620
  ).rejects.toThrow('Network error')
589
621
  })
590
622
 
591
- it('should not dispatch state update if API call fails', async () => {
623
+ it('should rollback optimistic state update if API call fails', async () => {
592
624
  instance.sharingCol.setReadOnly.mockRejectedValue(
593
625
  new Error('Network error')
594
626
  )
@@ -597,7 +629,89 @@ describe('updateSharingMemberType', () => {
597
629
  instance.updateSharingMemberType('sharing-123', 1, 'one-way')
598
630
  ).rejects.toThrow('Network error')
599
631
 
600
- expect(instance.setState).not.toHaveBeenCalled()
632
+ expect(instance.dispatch).toHaveBeenNthCalledWith(1, {
633
+ type: 'UPDATE_SHARING',
634
+ sharing: {
635
+ ...mockSharing,
636
+ attributes: {
637
+ ...mockSharing.attributes,
638
+ members: [
639
+ mockSharing.attributes.members[0],
640
+ {
641
+ ...mockSharing.attributes.members[1],
642
+ read_only: true
643
+ }
644
+ ]
645
+ }
646
+ }
647
+ })
648
+ expect(instance.dispatch).toHaveBeenNthCalledWith(2, {
649
+ type: 'UPDATE_SHARING',
650
+ sharing: {
651
+ ...mockSharing,
652
+ attributes: {
653
+ ...mockSharing.attributes,
654
+ members: [
655
+ mockSharing.attributes.members[0],
656
+ {
657
+ ...mockSharing.attributes.members[1],
658
+ read_only: false
659
+ }
660
+ ]
661
+ }
662
+ }
663
+ })
664
+ })
665
+
666
+ it('should preserve realtime changes when rolling back an API failure', async () => {
667
+ const realtimeSharing = {
668
+ ...mockSharing,
669
+ attributes: {
670
+ ...mockSharing.attributes,
671
+ description: 'updated by realtime',
672
+ members: [
673
+ {
674
+ ...mockSharing.attributes.members[0],
675
+ status: 'seen'
676
+ },
677
+ {
678
+ ...mockSharing.attributes.members[1],
679
+ read_only: true
680
+ }
681
+ ]
682
+ }
683
+ }
684
+
685
+ instance.sharingCol.setReadOnly.mockImplementation(async () => {
686
+ instance.state = {
687
+ ...instance.state,
688
+ sharings: instance.state.sharings.map(sharing =>
689
+ sharing.id === realtimeSharing.id ? realtimeSharing : sharing
690
+ )
691
+ }
692
+ throw new Error('Network error')
693
+ })
694
+
695
+ await expect(
696
+ instance.updateSharingMemberType('sharing-123', 1, 'one-way')
697
+ ).rejects.toThrow('Network error')
698
+
699
+ expect(instance.dispatch).toHaveBeenNthCalledWith(2, {
700
+ type: 'UPDATE_SHARING',
701
+ sharing: {
702
+ ...realtimeSharing,
703
+ attributes: {
704
+ ...realtimeSharing.attributes,
705
+ members: [
706
+ realtimeSharing.attributes.members[0],
707
+ {
708
+ ...realtimeSharing.attributes.members[1],
709
+ read_only: false
710
+ }
711
+ ]
712
+ }
713
+ }
714
+ })
601
715
  })
602
716
  })
603
717
 
@@ -74,6 +74,7 @@ const MemberRecipientPermissions = ({
74
74
  hideMenu()
75
75
  return
76
76
  }
77
+ hideMenu()
77
78
  try {
78
79
  await updateSharingMemberType(sharingId, memberIndex, newType)
79
80
  } catch (error) {
@@ -84,7 +85,6 @@ const MemberRecipientPermissions = ({
84
85
  variant: 'filled'
85
86
  })
86
87
  }
87
- hideMenu()
88
88
  },
89
89
  [
90
90
  hideMenu,
package/src/state.js CHANGED
@@ -258,6 +258,12 @@ const sharings = (state = [], action) => {
258
258
  case UPDATE_SHARING:
259
259
  case REVOKE_GROUP:
260
260
  case REVOKE_RECIPIENT:
261
+ if (
262
+ !isSharingADrive(action.sharing) &&
263
+ areAllRecipientsRevoked(action.sharing)
264
+ ) {
265
+ return state.filter(s => s.id !== action.sharing.id)
266
+ }
261
267
  return state.map(s => {
262
268
  return s.id !== action.sharing.id ? s : action.sharing
263
269
  })
package/src/state.spec.js CHANGED
@@ -343,6 +343,22 @@ describe('Sharing state', () => {
343
343
  )
344
344
  })
345
345
 
346
+ it('should forget a sharing when revoking the last recipient', () => {
347
+ const state = reducer(
348
+ reducer(
349
+ undefined,
350
+ receiveSharings({
351
+ sharings: [SHARING_1, SHARING_2]
352
+ })
353
+ ),
354
+ revokeRecipient(SHARING_2, 1)
355
+ )
356
+ expect(state.byDocId).toEqual({
357
+ folder_1: { sharings: [SHARING_1.id], permissions: [] }
358
+ })
359
+ expect(state.sharings).toEqual([SHARING_1])
360
+ })
361
+
346
362
  it('should revoke self', () => {
347
363
  const state = reducer(
348
364
  reducer(