cozy-sharing 28.5.0 → 28.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/SharingProvider.js +186 -87
- package/dist/SharingProvider.spec.js +152 -0
- package/dist/components/Recipient/LinkRecipient.js +27 -8
- package/dist/components/Recipient/LinkRecipient.stories.js +0 -1
- package/dist/components/Recipient/LinkRecipientPermissions.js +62 -15
- package/dist/components/Recipient/MemberRecipient.spec.js +13 -0
- package/dist/components/Recipient/MemberRecipientPermissions.js +80 -20
- package/dist/components/Recipient/MemberRecipientPermissions.spec.js +214 -0
- package/dist/components/Recipient/actions/revokeLink.js +34 -0
- package/dist/components/Recipient/actions/setReadOnlySharedPermission.js +0 -1
- package/dist/components/Recipient/actions/setReadOnlySharedPermission.spec.js +72 -0
- package/dist/components/Recipient/actions/setReadWriteSharedPermission.js +0 -1
- package/dist/components/Recipient/actions/setReadWriteSharedPermission.spec.js +72 -0
- package/dist/components/ShareDialogCozyToCozy.spec.js +9 -0
- package/dist/components/WhoHasAccess.js +0 -1
- package/locales/en.json +4 -1
- package/locales/fr.json +4 -1
- package/locales/ru.json +4 -1
- package/locales/vi.json +4 -1
- package/package.json +2 -2
- package/src/SharingProvider.jsx +52 -1
- package/src/SharingProvider.spec.jsx +94 -0
- package/src/components/Recipient/LinkRecipient.jsx +30 -7
- package/src/components/Recipient/LinkRecipient.stories.jsx +0 -1
- package/src/components/Recipient/LinkRecipientPermissions.jsx +54 -16
- package/src/components/Recipient/MemberRecipient.spec.jsx +11 -0
- package/src/components/Recipient/MemberRecipientPermissions.jsx +69 -16
- package/src/components/Recipient/MemberRecipientPermissions.spec.jsx +165 -0
- package/src/components/Recipient/actions/revokeLink.js +33 -0
- package/src/components/Recipient/actions/setReadOnlySharedPermission.js +1 -1
- package/src/components/Recipient/actions/setReadOnlySharedPermission.spec.jsx +95 -0
- package/src/components/Recipient/actions/setReadWriteSharedPermission.js +1 -1
- package/src/components/Recipient/actions/setReadWriteSharedPermission.spec.jsx +95 -0
- package/src/components/ShareDialogCozyToCozy.spec.jsx +6 -0
- package/src/components/WhoHasAccess.jsx +0 -1
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
|
|
2
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
3
|
+
import _regeneratorRuntime from "@babel/runtime/regenerator";
|
|
4
|
+
import { render, fireEvent, screen, act, waitFor } from '@testing-library/react';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { createMockClient } from 'cozy-client';
|
|
7
|
+
import MemberRecipientPermissions from './MemberRecipientPermissions';
|
|
8
|
+
import AppLike from '../../../test/AppLike';
|
|
9
|
+
var mockUpdateSharingMemberType = jest.fn();
|
|
10
|
+
var mockShowAlert = jest.fn();
|
|
11
|
+
jest.mock('../../hooks/useSharingContext', function () {
|
|
12
|
+
return {
|
|
13
|
+
useSharingContext: function useSharingContext() {
|
|
14
|
+
return {
|
|
15
|
+
updateSharingMemberType: mockUpdateSharingMemberType
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
jest.mock('cozy-ui/transpiled/react/providers/Alert', function () {
|
|
21
|
+
return {
|
|
22
|
+
__esModule: true,
|
|
23
|
+
default: function _default(_ref) {
|
|
24
|
+
var children = _ref.children;
|
|
25
|
+
return children;
|
|
26
|
+
},
|
|
27
|
+
useAlert: function useAlert() {
|
|
28
|
+
return {
|
|
29
|
+
showAlert: mockShowAlert
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
describe('MemberRecipientPermissions component', function () {
|
|
35
|
+
var client = createMockClient({});
|
|
36
|
+
client.options = {
|
|
37
|
+
uri: 'http://cozy.local:8080'
|
|
38
|
+
};
|
|
39
|
+
var defaultProps = {
|
|
40
|
+
isOwner: true,
|
|
41
|
+
isSharedDrive: false,
|
|
42
|
+
status: 'ready',
|
|
43
|
+
instance: 'http://cozy.local:8080',
|
|
44
|
+
type: 'two-way',
|
|
45
|
+
document: {
|
|
46
|
+
id: 'doc-123',
|
|
47
|
+
type: 'io.cozy.files',
|
|
48
|
+
path: '/test'
|
|
49
|
+
},
|
|
50
|
+
sharingId: 'sharing-123',
|
|
51
|
+
memberIndex: 1,
|
|
52
|
+
onRevoke: jest.fn(),
|
|
53
|
+
onRevokeSelf: jest.fn()
|
|
54
|
+
};
|
|
55
|
+
beforeEach(function () {
|
|
56
|
+
jest.clearAllMocks();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
var setup = function setup(props) {
|
|
60
|
+
return render( /*#__PURE__*/React.createElement(AppLike, {
|
|
61
|
+
client: client
|
|
62
|
+
}, /*#__PURE__*/React.createElement(MemberRecipientPermissions, _extends({}, defaultProps, props))));
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
var createRangeBackup;
|
|
66
|
+
beforeAll(function () {
|
|
67
|
+
createRangeBackup = global.document.createRange;
|
|
68
|
+
global.document.createRange = jest.fn(function () {
|
|
69
|
+
return {
|
|
70
|
+
setStart: function setStart() {},
|
|
71
|
+
setEnd: function setEnd() {},
|
|
72
|
+
commonAncestorContainer: {
|
|
73
|
+
nodeName: 'BODY',
|
|
74
|
+
ownerDocument: document
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
afterAll(function () {
|
|
80
|
+
global.document.createRange = createRangeBackup;
|
|
81
|
+
});
|
|
82
|
+
it('should render the current permission type', function () {
|
|
83
|
+
setup();
|
|
84
|
+
expect(screen.getByText('can change')).toBeInTheDocument();
|
|
85
|
+
});
|
|
86
|
+
it('should call updateSharingMemberType when changing to one-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
|
|
87
|
+
var _setup, getByText;
|
|
88
|
+
|
|
89
|
+
return _regeneratorRuntime.wrap(function _callee$(_context) {
|
|
90
|
+
while (1) switch (_context.prev = _context.next) {
|
|
91
|
+
case 0:
|
|
92
|
+
mockUpdateSharingMemberType.mockResolvedValue(undefined);
|
|
93
|
+
_setup = setup({
|
|
94
|
+
type: 'two-way'
|
|
95
|
+
}), getByText = _setup.getByText;
|
|
96
|
+
fireEvent.click(getByText('can change'));
|
|
97
|
+
fireEvent.click(getByText('Can view'));
|
|
98
|
+
expect(mockUpdateSharingMemberType).toHaveBeenCalledWith('sharing-123', 1, 'one-way');
|
|
99
|
+
|
|
100
|
+
case 5:
|
|
101
|
+
case "end":
|
|
102
|
+
return _context.stop();
|
|
103
|
+
}
|
|
104
|
+
}, _callee);
|
|
105
|
+
})));
|
|
106
|
+
it('should call updateSharingMemberType when changing to two-way', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
|
|
107
|
+
var _setup2, getByText;
|
|
108
|
+
|
|
109
|
+
return _regeneratorRuntime.wrap(function _callee2$(_context2) {
|
|
110
|
+
while (1) switch (_context2.prev = _context2.next) {
|
|
111
|
+
case 0:
|
|
112
|
+
mockUpdateSharingMemberType.mockResolvedValue(undefined);
|
|
113
|
+
_setup2 = setup({
|
|
114
|
+
type: 'one-way'
|
|
115
|
+
}), getByText = _setup2.getByText;
|
|
116
|
+
fireEvent.click(getByText('can view'));
|
|
117
|
+
fireEvent.click(getByText('Can change'));
|
|
118
|
+
expect(mockUpdateSharingMemberType).toHaveBeenCalledWith('sharing-123', 1, 'two-way');
|
|
119
|
+
|
|
120
|
+
case 5:
|
|
121
|
+
case "end":
|
|
122
|
+
return _context2.stop();
|
|
123
|
+
}
|
|
124
|
+
}, _callee2);
|
|
125
|
+
})));
|
|
126
|
+
it('should show error alert when updateSharingMemberType fails', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4() {
|
|
127
|
+
var _setup3, getByText;
|
|
128
|
+
|
|
129
|
+
return _regeneratorRuntime.wrap(function _callee4$(_context4) {
|
|
130
|
+
while (1) switch (_context4.prev = _context4.next) {
|
|
131
|
+
case 0:
|
|
132
|
+
mockUpdateSharingMemberType.mockRejectedValue(new Error('Network error'));
|
|
133
|
+
_setup3 = setup({
|
|
134
|
+
type: 'two-way'
|
|
135
|
+
}), getByText = _setup3.getByText;
|
|
136
|
+
_context4.next = 4;
|
|
137
|
+
return act( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
|
|
138
|
+
return _regeneratorRuntime.wrap(function _callee3$(_context3) {
|
|
139
|
+
while (1) switch (_context3.prev = _context3.next) {
|
|
140
|
+
case 0:
|
|
141
|
+
fireEvent.click(getByText('can change'));
|
|
142
|
+
fireEvent.click(getByText('Can view'));
|
|
143
|
+
|
|
144
|
+
case 2:
|
|
145
|
+
case "end":
|
|
146
|
+
return _context3.stop();
|
|
147
|
+
}
|
|
148
|
+
}, _callee3);
|
|
149
|
+
})));
|
|
150
|
+
|
|
151
|
+
case 4:
|
|
152
|
+
_context4.next = 6;
|
|
153
|
+
return waitFor(function () {
|
|
154
|
+
expect(mockShowAlert).toHaveBeenCalledWith({
|
|
155
|
+
message: 'An error occurred while changing the permissions. Please try again.',
|
|
156
|
+
severity: 'error',
|
|
157
|
+
variant: 'filled'
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
case 6:
|
|
162
|
+
case "end":
|
|
163
|
+
return _context4.stop();
|
|
164
|
+
}
|
|
165
|
+
}, _callee4);
|
|
166
|
+
})));
|
|
167
|
+
it('should not call updateSharingMemberType when selecting same type', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5() {
|
|
168
|
+
var _setup4, getByText;
|
|
169
|
+
|
|
170
|
+
return _regeneratorRuntime.wrap(function _callee5$(_context5) {
|
|
171
|
+
while (1) switch (_context5.prev = _context5.next) {
|
|
172
|
+
case 0:
|
|
173
|
+
mockUpdateSharingMemberType.mockResolvedValue(undefined);
|
|
174
|
+
_setup4 = setup({
|
|
175
|
+
type: 'two-way'
|
|
176
|
+
}), getByText = _setup4.getByText;
|
|
177
|
+
fireEvent.click(getByText('can change'));
|
|
178
|
+
fireEvent.click(getByText('Can change'));
|
|
179
|
+
expect(mockUpdateSharingMemberType).not.toHaveBeenCalled();
|
|
180
|
+
|
|
181
|
+
case 5:
|
|
182
|
+
case "end":
|
|
183
|
+
return _context5.stop();
|
|
184
|
+
}
|
|
185
|
+
}, _callee5);
|
|
186
|
+
})));
|
|
187
|
+
it('should not render menu when user is not owner and not the shared drive contact', function () {
|
|
188
|
+
var _setup5 = setup({
|
|
189
|
+
isOwner: false,
|
|
190
|
+
instance: 'http://other-cozy.local:8080'
|
|
191
|
+
}),
|
|
192
|
+
queryByText = _setup5.queryByText;
|
|
193
|
+
|
|
194
|
+
expect(queryByText('can change')).not.toBeInTheDocument();
|
|
195
|
+
expect(queryByText('can view')).not.toBeInTheDocument();
|
|
196
|
+
});
|
|
197
|
+
it('should render menu for owner', function () {
|
|
198
|
+
var _setup6 = setup({
|
|
199
|
+
isOwner: true
|
|
200
|
+
}),
|
|
201
|
+
getByText = _setup6.getByText;
|
|
202
|
+
|
|
203
|
+
expect(getByText('can change')).toBeInTheDocument();
|
|
204
|
+
});
|
|
205
|
+
it('should render menu for the user themselves', function () {
|
|
206
|
+
var _setup7 = setup({
|
|
207
|
+
isOwner: false,
|
|
208
|
+
instance: 'http://cozy.local:8080'
|
|
209
|
+
}),
|
|
210
|
+
getByText = _setup7.getByText;
|
|
211
|
+
|
|
212
|
+
expect(getByText('can change')).toBeInTheDocument();
|
|
213
|
+
});
|
|
214
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import _extends from "@babel/runtime/helpers/extends";
|
|
2
|
+
import React, { forwardRef } from 'react';
|
|
3
|
+
import ActionsMenuItem from 'cozy-ui/transpiled/react/ActionsMenu/ActionsMenuItem';
|
|
4
|
+
import Icon from 'cozy-ui/transpiled/react/Icon';
|
|
5
|
+
import TrashIcon from 'cozy-ui/transpiled/react/Icons/Trash';
|
|
6
|
+
import ListItemIcon from 'cozy-ui/transpiled/react/ListItemIcon';
|
|
7
|
+
import ListItemText from 'cozy-ui/transpiled/react/ListItemText';
|
|
8
|
+
import Typography from 'cozy-ui/transpiled/react/Typography';
|
|
9
|
+
|
|
10
|
+
var revokeLink = function revokeLink(_ref) {
|
|
11
|
+
var t = _ref.t,
|
|
12
|
+
handleRevocation = _ref.handleRevocation;
|
|
13
|
+
var title = t('Share.permissionLink.deactivate');
|
|
14
|
+
return {
|
|
15
|
+
name: 'revokeLink',
|
|
16
|
+
label: title,
|
|
17
|
+
icon: TrashIcon,
|
|
18
|
+
action: handleRevocation,
|
|
19
|
+
Component: /*#__PURE__*/forwardRef(function RevokeLinkItem(props, ref) {
|
|
20
|
+
return /*#__PURE__*/React.createElement(ActionsMenuItem, _extends({}, props, {
|
|
21
|
+
ref: ref
|
|
22
|
+
}), /*#__PURE__*/React.createElement(ListItemIcon, null, /*#__PURE__*/React.createElement(Icon, {
|
|
23
|
+
icon: TrashIcon,
|
|
24
|
+
color: "var(--errorColor)"
|
|
25
|
+
})), /*#__PURE__*/React.createElement(ListItemText, {
|
|
26
|
+
primary: /*#__PURE__*/React.createElement(Typography, {
|
|
27
|
+
color: "error"
|
|
28
|
+
}, title)
|
|
29
|
+
}));
|
|
30
|
+
})
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export { revokeLink };
|
|
@@ -12,7 +12,6 @@ var makeComponent = function makeComponent(_ref) {
|
|
|
12
12
|
var Component = /*#__PURE__*/forwardRef(function (props, ref) {
|
|
13
13
|
return /*#__PURE__*/React.createElement(ActionsMenuItem, _extends({}, props, {
|
|
14
14
|
ref: ref,
|
|
15
|
-
button: false,
|
|
16
15
|
onClick: onClick
|
|
17
16
|
}), /*#__PURE__*/React.createElement(ListItemIcon, null, /*#__PURE__*/React.createElement(Radios, {
|
|
18
17
|
checked: type === 'one-way'
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { setReadOnlySharedPermission } from './setReadOnlySharedPermission';
|
|
4
|
+
import AppLike from '../../../../test/AppLike';
|
|
5
|
+
|
|
6
|
+
var mockT = function mockT(key) {
|
|
7
|
+
var translations = {
|
|
8
|
+
'Share.type.one-way': 'Can view',
|
|
9
|
+
'Share.type.two-way': 'Can change'
|
|
10
|
+
};
|
|
11
|
+
return translations[key] || key;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
describe('setReadOnlySharedPermission', function () {
|
|
15
|
+
var setType;
|
|
16
|
+
beforeEach(function () {
|
|
17
|
+
setType = jest.fn();
|
|
18
|
+
});
|
|
19
|
+
it('should return correct action structure', function () {
|
|
20
|
+
var action = setReadOnlySharedPermission({
|
|
21
|
+
t: mockT,
|
|
22
|
+
type: 'one-way',
|
|
23
|
+
setType: setType
|
|
24
|
+
});
|
|
25
|
+
expect(action.name).toBe('setReadOnlySharedPermission');
|
|
26
|
+
expect(action.label).toBe('Can view');
|
|
27
|
+
expect(action.icon).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
it('should call setType with one-way when action is triggered', function () {
|
|
30
|
+
var action = setReadOnlySharedPermission({
|
|
31
|
+
t: mockT,
|
|
32
|
+
type: 'two-way',
|
|
33
|
+
setType: setType
|
|
34
|
+
});
|
|
35
|
+
action.action();
|
|
36
|
+
expect(setType).toHaveBeenCalledWith('one-way');
|
|
37
|
+
});
|
|
38
|
+
it('should render radio as checked when type is one-way', function () {
|
|
39
|
+
var _setReadOnlySharedPer = setReadOnlySharedPermission({
|
|
40
|
+
t: mockT,
|
|
41
|
+
type: 'one-way',
|
|
42
|
+
setType: setType
|
|
43
|
+
}),
|
|
44
|
+
Component = _setReadOnlySharedPer.Component;
|
|
45
|
+
|
|
46
|
+
render( /*#__PURE__*/React.createElement(AppLike, null, /*#__PURE__*/React.createElement(Component, null)));
|
|
47
|
+
expect(screen.getByRole('radio')).toBeChecked();
|
|
48
|
+
});
|
|
49
|
+
it('should render radio as unchecked when type is two-way', function () {
|
|
50
|
+
var _setReadOnlySharedPer2 = setReadOnlySharedPermission({
|
|
51
|
+
t: mockT,
|
|
52
|
+
type: 'two-way',
|
|
53
|
+
setType: setType
|
|
54
|
+
}),
|
|
55
|
+
Component = _setReadOnlySharedPer2.Component;
|
|
56
|
+
|
|
57
|
+
render( /*#__PURE__*/React.createElement(AppLike, null, /*#__PURE__*/React.createElement(Component, null)));
|
|
58
|
+
expect(screen.getByRole('radio')).not.toBeChecked();
|
|
59
|
+
});
|
|
60
|
+
it('should call setType with one-way when the component is clicked', function () {
|
|
61
|
+
var _setReadOnlySharedPer3 = setReadOnlySharedPermission({
|
|
62
|
+
t: mockT,
|
|
63
|
+
type: 'two-way',
|
|
64
|
+
setType: setType
|
|
65
|
+
}),
|
|
66
|
+
Component = _setReadOnlySharedPer3.Component;
|
|
67
|
+
|
|
68
|
+
render( /*#__PURE__*/React.createElement(AppLike, null, /*#__PURE__*/React.createElement(Component, null)));
|
|
69
|
+
fireEvent.click(screen.getByText('Can view'));
|
|
70
|
+
expect(setType).toHaveBeenCalledWith('one-way');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -12,7 +12,6 @@ var makeComponent = function makeComponent(_ref) {
|
|
|
12
12
|
var Component = /*#__PURE__*/forwardRef(function (props, ref) {
|
|
13
13
|
return /*#__PURE__*/React.createElement(ActionsMenuItem, _extends({}, props, {
|
|
14
14
|
ref: ref,
|
|
15
|
-
button: false,
|
|
16
15
|
onClick: onClick
|
|
17
16
|
}), /*#__PURE__*/React.createElement(ListItemIcon, null, /*#__PURE__*/React.createElement(Radios, {
|
|
18
17
|
checked: type === 'two-way'
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { setReadWriteSharedPermission } from './setReadWriteSharedPermission';
|
|
4
|
+
import AppLike from '../../../../test/AppLike';
|
|
5
|
+
|
|
6
|
+
var mockT = function mockT(key) {
|
|
7
|
+
var translations = {
|
|
8
|
+
'Share.type.one-way': 'Can view',
|
|
9
|
+
'Share.type.two-way': 'Can change'
|
|
10
|
+
};
|
|
11
|
+
return translations[key] || key;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
describe('setReadWriteSharedPermission', function () {
|
|
15
|
+
var setType;
|
|
16
|
+
beforeEach(function () {
|
|
17
|
+
setType = jest.fn();
|
|
18
|
+
});
|
|
19
|
+
it('should return correct action structure', function () {
|
|
20
|
+
var action = setReadWriteSharedPermission({
|
|
21
|
+
t: mockT,
|
|
22
|
+
type: 'two-way',
|
|
23
|
+
setType: setType
|
|
24
|
+
});
|
|
25
|
+
expect(action.name).toBe('setReadWriteSharedPermission');
|
|
26
|
+
expect(action.label).toBe('Can change');
|
|
27
|
+
expect(action.icon).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
it('should call setType with two-way when action is triggered', function () {
|
|
30
|
+
var action = setReadWriteSharedPermission({
|
|
31
|
+
t: mockT,
|
|
32
|
+
type: 'one-way',
|
|
33
|
+
setType: setType
|
|
34
|
+
});
|
|
35
|
+
action.action();
|
|
36
|
+
expect(setType).toHaveBeenCalledWith('two-way');
|
|
37
|
+
});
|
|
38
|
+
it('should render radio as checked when type is two-way', function () {
|
|
39
|
+
var _setReadWriteSharedPe = setReadWriteSharedPermission({
|
|
40
|
+
t: mockT,
|
|
41
|
+
type: 'two-way',
|
|
42
|
+
setType: setType
|
|
43
|
+
}),
|
|
44
|
+
Component = _setReadWriteSharedPe.Component;
|
|
45
|
+
|
|
46
|
+
render( /*#__PURE__*/React.createElement(AppLike, null, /*#__PURE__*/React.createElement(Component, null)));
|
|
47
|
+
expect(screen.getByRole('radio')).toBeChecked();
|
|
48
|
+
});
|
|
49
|
+
it('should render radio as unchecked when type is one-way', function () {
|
|
50
|
+
var _setReadWriteSharedPe2 = setReadWriteSharedPermission({
|
|
51
|
+
t: mockT,
|
|
52
|
+
type: 'one-way',
|
|
53
|
+
setType: setType
|
|
54
|
+
}),
|
|
55
|
+
Component = _setReadWriteSharedPe2.Component;
|
|
56
|
+
|
|
57
|
+
render( /*#__PURE__*/React.createElement(AppLike, null, /*#__PURE__*/React.createElement(Component, null)));
|
|
58
|
+
expect(screen.getByRole('radio')).not.toBeChecked();
|
|
59
|
+
});
|
|
60
|
+
it('should call setType with two-way when the component is clicked', function () {
|
|
61
|
+
var _setReadWriteSharedPe3 = setReadWriteSharedPermission({
|
|
62
|
+
t: mockT,
|
|
63
|
+
type: 'one-way',
|
|
64
|
+
setType: setType
|
|
65
|
+
}),
|
|
66
|
+
Component = _setReadWriteSharedPe3.Component;
|
|
67
|
+
|
|
68
|
+
render( /*#__PURE__*/React.createElement(AppLike, null, /*#__PURE__*/React.createElement(Component, null)));
|
|
69
|
+
fireEvent.click(screen.getByText('Can change'));
|
|
70
|
+
expect(setType).toHaveBeenCalledWith('two-way');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -29,6 +29,15 @@ jest.mock('cozy-client', function () {
|
|
|
29
29
|
}
|
|
30
30
|
});
|
|
31
31
|
});
|
|
32
|
+
jest.mock('../hooks/useSharingContext', function () {
|
|
33
|
+
return {
|
|
34
|
+
useSharingContext: function useSharingContext() {
|
|
35
|
+
return {
|
|
36
|
+
updateSharingMemberType: jest.fn()
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
});
|
|
32
41
|
describe('ShareDialogCozyToCozy', function () {
|
|
33
42
|
var client = createMockClient({});
|
|
34
43
|
client.options = {
|
|
@@ -55,7 +55,6 @@ var WhoHasAccess = function WhoHasAccess(_ref2) {
|
|
|
55
55
|
documentType: documentType,
|
|
56
56
|
onRevoke: onRevoke,
|
|
57
57
|
onRevokeSelf: onRevokeSelf,
|
|
58
|
-
link: link,
|
|
59
58
|
permissions: permissions,
|
|
60
59
|
fadeIn: linkHasBeenJustCreated
|
|
61
60
|
}), /*#__PURE__*/React.createElement(OwnerRecipient, {
|
package/locales/en.json
CHANGED
|
@@ -34,7 +34,10 @@
|
|
|
34
34
|
"members": {
|
|
35
35
|
"count": "%{smart_count} member |||| %{smart_count} members",
|
|
36
36
|
"others": "and 1 other… |||| and %{smart_count} others…",
|
|
37
|
-
"otherContacts": "other contact |||| other contacts"
|
|
37
|
+
"otherContacts": "other contact |||| other contacts",
|
|
38
|
+
"error": {
|
|
39
|
+
"changePermission": "An error occurred while changing the permissions. Please try again."
|
|
40
|
+
}
|
|
38
41
|
},
|
|
39
42
|
"contacts": {
|
|
40
43
|
"whoHasAccess": "Who has access?",
|
package/locales/fr.json
CHANGED
|
@@ -34,7 +34,10 @@
|
|
|
34
34
|
"members": {
|
|
35
35
|
"count": "%{smart_count} membre |||| %{smart_count} membres",
|
|
36
36
|
"others": "et 1 autre… |||| et %{smart_count} autres…",
|
|
37
|
-
"otherContacts": "autre contact |||| autres contacts"
|
|
37
|
+
"otherContacts": "autre contact |||| autres contacts",
|
|
38
|
+
"error": {
|
|
39
|
+
"changePermission": "Une erreur s'est produite lors du changement des permissions. Veuillez réessayer."
|
|
40
|
+
}
|
|
38
41
|
},
|
|
39
42
|
"contacts": {
|
|
40
43
|
"whoHasAccess": "Qui a accès ?",
|
package/locales/ru.json
CHANGED
|
@@ -34,7 +34,10 @@
|
|
|
34
34
|
"members": {
|
|
35
35
|
"count": "%{smart_count} участник |||| %{smart_count} участника |||| %{smart_count} участников",
|
|
36
36
|
"others": "и 1 другой… |||| и %{smart_count} других…",
|
|
37
|
-
"otherContacts": "другой контакт |||| других контакта"
|
|
37
|
+
"otherContacts": "другой контакт |||| других контакта",
|
|
38
|
+
"error": {
|
|
39
|
+
"changePermission": "Произошла ошибка при изменении разрешений. Пожалуйста, попробуйте снова."
|
|
40
|
+
}
|
|
38
41
|
},
|
|
39
42
|
"contacts": {
|
|
40
43
|
"whoHasAccess": "У кого есть доступ?",
|
package/locales/vi.json
CHANGED
|
@@ -34,7 +34,10 @@
|
|
|
34
34
|
"members": {
|
|
35
35
|
"count": "%{smart_count} thành viên |||| %{smart_count} thành viên",
|
|
36
36
|
"others": "và 1 người khác… |||| và %{smart_count} người khác…",
|
|
37
|
-
"otherContacts": "người liên hệ khác |||| người liên hệ khác"
|
|
37
|
+
"otherContacts": "người liên hệ khác |||| người liên hệ khác",
|
|
38
|
+
"error": {
|
|
39
|
+
"changePermission": "Đã xảy ra lỗi khi thay đổi quyền. Vui lòng thử lại."
|
|
40
|
+
}
|
|
38
41
|
},
|
|
39
42
|
"contacts": {
|
|
40
43
|
"whoHasAccess": "Ai có quyền truy cập?",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cozy-sharing",
|
|
3
|
-
"version": "28.
|
|
3
|
+
"version": "28.7.0",
|
|
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": "
|
|
86
|
+
"gitHead": "421077ecc24c9a15a3a1c22d9c3442917292f8ba"
|
|
87
87
|
}
|
package/src/SharingProvider.jsx
CHANGED
|
@@ -94,7 +94,8 @@ export class SharingProvider extends Component {
|
|
|
94
94
|
revokeAllRecipients: this.revokeAllRecipients,
|
|
95
95
|
refresh: this.fetchAllSharings,
|
|
96
96
|
hasWriteAccess: this.hasWriteAccess,
|
|
97
|
-
renameSharedDrive: this.renameSharedDrive
|
|
97
|
+
renameSharedDrive: this.renameSharedDrive,
|
|
98
|
+
updateSharingMemberType: this.updateSharingMemberType
|
|
98
99
|
}
|
|
99
100
|
this.isPublic = props.isPublic ?? false
|
|
100
101
|
this.realtime = null
|
|
@@ -339,6 +340,55 @@ export class SharingProvider extends Component {
|
|
|
339
340
|
this.dispatch(revokeSelf(sharing))
|
|
340
341
|
}
|
|
341
342
|
|
|
343
|
+
/**
|
|
344
|
+
* Update a sharing member's read_only field to change permission type
|
|
345
|
+
* @param {string} sharingId - The ID of the sharing
|
|
346
|
+
* @param {number} memberIndex - The index of the member in the sharing members array
|
|
347
|
+
* @param {string} newType - 'one-way' for read-only, 'two-way' for read-write
|
|
348
|
+
*/
|
|
349
|
+
updateSharingMemberType = async (sharingId, memberIndex, newType) => {
|
|
350
|
+
const sharing = getSharingById(this.state, sharingId)
|
|
351
|
+
if (!sharing) throw new Error('Sharing not found')
|
|
352
|
+
|
|
353
|
+
const member = sharing.attributes.members[memberIndex]
|
|
354
|
+
if (!member) throw new Error('Member not found')
|
|
355
|
+
|
|
356
|
+
// Find the contact by email (same pattern as helpers/contacts.js)
|
|
357
|
+
const matchedContacts = await this.props.client
|
|
358
|
+
.collection('io.cozy.contacts')
|
|
359
|
+
.find(
|
|
360
|
+
{
|
|
361
|
+
email: { $elemMatch: { address: member.email } },
|
|
362
|
+
_id: { $gt: null }
|
|
363
|
+
},
|
|
364
|
+
{ indexedFields: ['_id'] }
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
if (!matchedContacts.data.length) {
|
|
368
|
+
throw new Error(`Contact not found for member ${member.email}`)
|
|
369
|
+
}
|
|
370
|
+
const contact = matchedContacts.data[0]
|
|
371
|
+
|
|
372
|
+
// Revoke (API call only, no state dispatch to avoid UI flickering)
|
|
373
|
+
await this.sharingCol.revokeRecipient(sharing, memberIndex)
|
|
374
|
+
|
|
375
|
+
// Re-add with the new permission type
|
|
376
|
+
const readOnly = newType === 'one-way'
|
|
377
|
+
try {
|
|
378
|
+
await this.addRecipients({
|
|
379
|
+
document: sharing,
|
|
380
|
+
recipients: readOnly ? [] : [contact],
|
|
381
|
+
readOnlyRecipients: readOnly ? [contact] : []
|
|
382
|
+
})
|
|
383
|
+
} catch (error) {
|
|
384
|
+
log.error(
|
|
385
|
+
`Failed to re-add member ${member.email} after revocation. The member has been revoked but could not be re-added with the new permission type.`,
|
|
386
|
+
error
|
|
387
|
+
)
|
|
388
|
+
throw error
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
342
392
|
shareByLink = async (document, options) => {
|
|
343
393
|
const resp = document.driveId
|
|
344
394
|
? await this.createSharedDriveSharingLink(document, options)
|
|
@@ -438,6 +488,7 @@ export class SharingProvider extends Component {
|
|
|
438
488
|
updatedPermissions,
|
|
439
489
|
{ expiresAt, password }
|
|
440
490
|
)
|
|
491
|
+
this.dispatch(updateSharingLink(resp.data))
|
|
441
492
|
return resp
|
|
442
493
|
})
|
|
443
494
|
)
|
|
@@ -234,6 +234,100 @@ describe('updateDocumentPermissions', () => {
|
|
|
234
234
|
})
|
|
235
235
|
})
|
|
236
236
|
|
|
237
|
+
describe('updateSharingMemberType', () => {
|
|
238
|
+
const mockContact = { _id: 'contact-1', email: [{ address: 'bob@bob.cozy' }] }
|
|
239
|
+
const mockSharing = {
|
|
240
|
+
id: 'sharing-123',
|
|
241
|
+
type: 'io.cozy.sharings',
|
|
242
|
+
attributes: {
|
|
243
|
+
members: [
|
|
244
|
+
{ status: 'owner', email: 'owner@cozy.local' },
|
|
245
|
+
{ status: 'ready', email: 'bob@bob.cozy' }
|
|
246
|
+
]
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
let instance
|
|
251
|
+
let mockContactsCollection
|
|
252
|
+
|
|
253
|
+
beforeEach(() => {
|
|
254
|
+
mockContactsCollection = {
|
|
255
|
+
find: jest.fn().mockResolvedValue({ data: [mockContact] })
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const mockClient = {
|
|
259
|
+
getStackClient: () => ({ uri: 'http://cozy.local' }),
|
|
260
|
+
collection: jest.fn().mockReturnValue(mockContactsCollection)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
instance = new SharingProvider({ client: mockClient })
|
|
264
|
+
instance.state = {
|
|
265
|
+
...instance.state,
|
|
266
|
+
sharings: [mockSharing]
|
|
267
|
+
}
|
|
268
|
+
instance.sharingCol = {
|
|
269
|
+
revokeRecipient: jest.fn().mockResolvedValue({})
|
|
270
|
+
}
|
|
271
|
+
jest.spyOn(instance, 'addRecipients').mockResolvedValue({})
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
it('should throw when sharing is not found', async () => {
|
|
275
|
+
await expect(
|
|
276
|
+
instance.updateSharingMemberType('unknown-id', 1, 'one-way')
|
|
277
|
+
).rejects.toThrow('Sharing not found')
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
it('should throw when member is not found', async () => {
|
|
281
|
+
await expect(
|
|
282
|
+
instance.updateSharingMemberType('sharing-123', 99, 'one-way')
|
|
283
|
+
).rejects.toThrow('Member not found')
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
it('should throw when contact is not found', async () => {
|
|
287
|
+
mockContactsCollection.find.mockResolvedValue({ data: [] })
|
|
288
|
+
|
|
289
|
+
await expect(
|
|
290
|
+
instance.updateSharingMemberType('sharing-123', 1, 'one-way')
|
|
291
|
+
).rejects.toThrow('Contact not found')
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
it('should revoke and re-add with readOnly when switching to one-way', async () => {
|
|
295
|
+
await instance.updateSharingMemberType('sharing-123', 1, 'one-way')
|
|
296
|
+
|
|
297
|
+
expect(instance.sharingCol.revokeRecipient).toHaveBeenCalledWith(
|
|
298
|
+
mockSharing,
|
|
299
|
+
1
|
|
300
|
+
)
|
|
301
|
+
expect(instance.addRecipients).toHaveBeenCalledWith({
|
|
302
|
+
document: mockSharing,
|
|
303
|
+
recipients: [],
|
|
304
|
+
readOnlyRecipients: [mockContact]
|
|
305
|
+
})
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
it('should revoke and re-add as read-write when switching to two-way', async () => {
|
|
309
|
+
await instance.updateSharingMemberType('sharing-123', 1, 'two-way')
|
|
310
|
+
|
|
311
|
+
expect(instance.sharingCol.revokeRecipient).toHaveBeenCalledWith(
|
|
312
|
+
mockSharing,
|
|
313
|
+
1
|
|
314
|
+
)
|
|
315
|
+
expect(instance.addRecipients).toHaveBeenCalledWith({
|
|
316
|
+
document: mockSharing,
|
|
317
|
+
recipients: [mockContact],
|
|
318
|
+
readOnlyRecipients: []
|
|
319
|
+
})
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
it('should rethrow error if addRecipients fails after revocation', async () => {
|
|
323
|
+
instance.addRecipients.mockRejectedValue(new Error('Network error'))
|
|
324
|
+
|
|
325
|
+
await expect(
|
|
326
|
+
instance.updateSharingMemberType('sharing-123', 1, 'one-way')
|
|
327
|
+
).rejects.toThrow('Network error')
|
|
328
|
+
})
|
|
329
|
+
})
|
|
330
|
+
|
|
237
331
|
// TODO Convert with react-testing-library
|
|
238
332
|
// describe('hasWriteAccess', () => {
|
|
239
333
|
// it('tells if a doc is writtable', () => {
|