cozy-sharing 30.3.0 → 31.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,252 @@
1
+ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
+ import _regeneratorRuntime from "@babel/runtime/regenerator";
3
+ import { renderHook, act } from '@testing-library/react-hooks';
4
+ import { useSharedDrive } from './useSharedDrive';
5
+ var mockShowAlert = jest.fn();
6
+ var mockCreateSharedDrive = jest.fn();
7
+ var mockCollection = jest.fn(function () {
8
+ return {
9
+ createSharedDrive: mockCreateSharedDrive
10
+ };
11
+ });
12
+ var mockT = jest.fn(function (key) {
13
+ return key;
14
+ });
15
+ jest.mock('cozy-client', function () {
16
+ return {
17
+ useClient: jest.fn(function () {
18
+ return {
19
+ collection: mockCollection
20
+ };
21
+ }),
22
+ models: {
23
+ contact: {
24
+ getPrimaryEmail: jest.fn(function (r) {
25
+ return Array.isArray(r.email) && r.email[0] ? r.email[0].address : '';
26
+ }),
27
+ getDisplayName: jest.fn(function (r) {
28
+ return r.displayName || r.name || '';
29
+ })
30
+ }
31
+ }
32
+ };
33
+ });
34
+ jest.mock('cozy-ui/transpiled/react/providers/Alert', function () {
35
+ return {
36
+ useAlert: jest.fn(function () {
37
+ return {
38
+ showAlert: mockShowAlert
39
+ };
40
+ })
41
+ };
42
+ });
43
+ jest.mock('twake-i18n', function () {
44
+ return {
45
+ useI18n: jest.fn(function () {
46
+ return {
47
+ t: mockT
48
+ };
49
+ })
50
+ };
51
+ });
52
+ jest.mock('../../models', function () {
53
+ return {
54
+ Contact: {
55
+ doctype: 'io.cozy.contacts'
56
+ }
57
+ };
58
+ });
59
+ describe('useSharedDrive', function () {
60
+ beforeEach(function () {
61
+ jest.clearAllMocks();
62
+ });
63
+ it('returns empty initial state', function () {
64
+ var _renderHook = renderHook(function () {
65
+ return useSharedDrive({
66
+ onSuccess: jest.fn()
67
+ });
68
+ }),
69
+ result = _renderHook.result;
70
+
71
+ expect(result.current.sharedDriveName).toBe('');
72
+ expect(result.current.recipients).toEqual([]);
73
+ });
74
+ it('handleSharedDriveNameChange updates the name', function () {
75
+ var _renderHook2 = renderHook(function () {
76
+ return useSharedDrive({
77
+ onSuccess: jest.fn()
78
+ });
79
+ }),
80
+ result = _renderHook2.result;
81
+
82
+ act(function () {
83
+ result.current.handleSharedDriveNameChange({
84
+ target: {
85
+ value: 'My Drive'
86
+ }
87
+ });
88
+ });
89
+ expect(result.current.sharedDriveName).toBe('My Drive');
90
+ });
91
+ it('onRevoke removes a recipient from both lists', function () {
92
+ var _renderHook3 = renderHook(function () {
93
+ return useSharedDrive({
94
+ onSuccess: jest.fn()
95
+ });
96
+ }),
97
+ result = _renderHook3.result;
98
+
99
+ act(function () {
100
+ result.current.onShare({
101
+ recipients: [{
102
+ _id: 'abc',
103
+ displayName: 'Alice',
104
+ email: []
105
+ }],
106
+ readOnlyRecipients: []
107
+ });
108
+ });
109
+ act(function () {
110
+ result.current.onRevoke("virtual-shared-drive-sharing-abc");
111
+ });
112
+ expect(result.current.recipients).toHaveLength(0);
113
+ });
114
+ it('onSetType moves a recipient to read-only', function () {
115
+ var _renderHook4 = renderHook(function () {
116
+ return useSharedDrive({
117
+ onSuccess: jest.fn()
118
+ });
119
+ }),
120
+ result = _renderHook4.result;
121
+
122
+ act(function () {
123
+ result.current.onShare({
124
+ recipients: [{
125
+ _id: 'abc',
126
+ displayName: 'Alice',
127
+ email: []
128
+ }],
129
+ readOnlyRecipients: []
130
+ });
131
+ });
132
+ act(function () {
133
+ result.current.onSetType("virtual-shared-drive-sharing-abc", 'one-way');
134
+ });
135
+ expect(result.current.recipients[0].type).toBe('one-way');
136
+ });
137
+ it('onSetType moves a recipient to read-write', function () {
138
+ var _renderHook5 = renderHook(function () {
139
+ return useSharedDrive({
140
+ onSuccess: jest.fn()
141
+ });
142
+ }),
143
+ result = _renderHook5.result;
144
+
145
+ act(function () {
146
+ result.current.onShare({
147
+ recipients: [],
148
+ readOnlyRecipients: [{
149
+ _id: 'xyz',
150
+ displayName: 'Bob',
151
+ email: []
152
+ }]
153
+ });
154
+ });
155
+ act(function () {
156
+ result.current.onSetType("virtual-shared-drive-sharing-xyz", 'two-way');
157
+ });
158
+ expect(result.current.recipients).toHaveLength(1);
159
+ expect(result.current.recipients[0].type).toBe('two-way');
160
+ });
161
+ it('onCreate calls createSharedDrive, showAlert with success, and onSuccess', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
162
+ var onSuccess, _renderHook6, result;
163
+
164
+ return _regeneratorRuntime.wrap(function _callee2$(_context2) {
165
+ while (1) switch (_context2.prev = _context2.next) {
166
+ case 0:
167
+ onSuccess = jest.fn();
168
+ mockCreateSharedDrive.mockResolvedValue({});
169
+ _renderHook6 = renderHook(function () {
170
+ return useSharedDrive({
171
+ onSuccess: onSuccess
172
+ });
173
+ }), result = _renderHook6.result;
174
+ act(function () {
175
+ result.current.handleSharedDriveNameChange({
176
+ target: {
177
+ value: 'My Drive'
178
+ }
179
+ });
180
+ });
181
+ _context2.next = 6;
182
+ return act( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
183
+ return _regeneratorRuntime.wrap(function _callee$(_context) {
184
+ while (1) switch (_context.prev = _context.next) {
185
+ case 0:
186
+ _context.next = 2;
187
+ return result.current.onCreate();
188
+
189
+ case 2:
190
+ case "end":
191
+ return _context.stop();
192
+ }
193
+ }, _callee);
194
+ })));
195
+
196
+ case 6:
197
+ expect(mockCreateSharedDrive).toHaveBeenCalledWith(expect.objectContaining({
198
+ name: 'My Drive',
199
+ description: 'My Drive'
200
+ }));
201
+ expect(mockShowAlert).toHaveBeenCalledWith(expect.objectContaining({
202
+ severity: 'success'
203
+ }));
204
+ expect(onSuccess).toHaveBeenCalledTimes(1);
205
+
206
+ case 9:
207
+ case "end":
208
+ return _context2.stop();
209
+ }
210
+ }, _callee2);
211
+ })));
212
+ it('onCreate shows error alert and does not call onSuccess on failure', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee4() {
213
+ var onSuccess, _renderHook7, result;
214
+
215
+ return _regeneratorRuntime.wrap(function _callee4$(_context4) {
216
+ while (1) switch (_context4.prev = _context4.next) {
217
+ case 0:
218
+ onSuccess = jest.fn();
219
+ mockCreateSharedDrive.mockRejectedValue(new Error('network error'));
220
+ _renderHook7 = renderHook(function () {
221
+ return useSharedDrive({
222
+ onSuccess: onSuccess
223
+ });
224
+ }), result = _renderHook7.result;
225
+ _context4.next = 5;
226
+ return act( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
227
+ return _regeneratorRuntime.wrap(function _callee3$(_context3) {
228
+ while (1) switch (_context3.prev = _context3.next) {
229
+ case 0:
230
+ _context3.next = 2;
231
+ return result.current.onCreate();
232
+
233
+ case 2:
234
+ case "end":
235
+ return _context3.stop();
236
+ }
237
+ }, _callee3);
238
+ })));
239
+
240
+ case 5:
241
+ expect(mockShowAlert).toHaveBeenCalledWith(expect.objectContaining({
242
+ severity: 'error'
243
+ }));
244
+ expect(onSuccess).not.toHaveBeenCalled();
245
+
246
+ case 7:
247
+ case "end":
248
+ return _context4.stop();
249
+ }
250
+ }, _callee4);
251
+ })));
252
+ });
package/dist/index.js CHANGED
@@ -16,6 +16,7 @@ export { default as OwnerRecipientDefaultLite } from './components/Recipient/Own
16
16
  export { SharedRecipientsList } from './SharedRecipientsList';
17
17
  export { ShareButton } from './ShareButton';
18
18
  export { SharedDriveModal } from './components/SharedDrive/SharedDriveModal';
19
+ export { SharedDriveForm } from './components/SharedDrive/SharedDriveForm';
19
20
  export { FederatedFolderModal } from './components/FederatedFolder/FederatedFolderModal';
20
21
  export { ShareModal } from './components/ShareModal/ShareModal';
21
22
  export { RefreshableSharings } from './RefreshableSharings';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-sharing",
3
- "version": "30.3.0",
3
+ "version": "31.0.0",
4
4
  "description": "Provides sharing login for React applications.",
5
5
  "main": "dist/index.js",
6
6
  "author": "Cozy",
@@ -54,7 +54,7 @@
54
54
  "babel-jest": "30.3.0",
55
55
  "babel-plugin-css-modules-transform": "1.6.2",
56
56
  "babel-plugin-inline-react-svg": "1.1.2",
57
- "cozy-client": "^60.22.0",
57
+ "cozy-client": "^60.24.0",
58
58
  "cozy-device-helper": "^3.7.1",
59
59
  "cozy-intent": "^2.31.1",
60
60
  "cozy-minilog": "^3.10.1",
@@ -69,7 +69,7 @@
69
69
  "twake-i18n": "^0.4.1"
70
70
  },
71
71
  "peerDependencies": {
72
- "cozy-client": ">=60.22.0",
72
+ "cozy-client": ">=60.24.0",
73
73
  "cozy-device-helper": ">=3.7.1",
74
74
  "cozy-flags": ">=4.8.1",
75
75
  "cozy-intent": ">=2.29.1",
@@ -84,5 +84,5 @@
84
84
  "sideEffects": [
85
85
  "*.css"
86
86
  ],
87
- "gitHead": "8b1c0aece9584d82862592b6ab6e5579fa30be19"
87
+ "gitHead": "435fe26df7f273dda07330473eb1f638f0f1277a"
88
88
  }
@@ -21,7 +21,6 @@ export const EditableSharingModal = ({ document, ...rest }) => {
21
21
  hasSharedChild,
22
22
  hasSharedParent,
23
23
  isOwner,
24
- isSharedDrive,
25
24
  revoke,
26
25
  revokeSelf,
27
26
  share
@@ -35,7 +34,6 @@ export const EditableSharingModal = ({ document, ...rest }) => {
35
34
  hasSharedChild={documentPath && hasSharedChild(documentPath)}
36
35
  hasSharedParent={documentPath && hasSharedParent(documentPath)}
37
36
  isOwner={isOwner(document._id)}
38
- isSharedDrive={isSharedDrive(document._id)}
39
37
  link={getSharingLink(document._id)}
40
38
  onRevoke={revoke}
41
39
  onRevokeSelf={revokeSelf}
@@ -5,7 +5,6 @@ import flag from 'cozy-flags'
5
5
 
6
6
  import ShareDialogCozyToCozy from './ShareDialogCozyToCozy'
7
7
  import ShareDialogOnlyByLink from './ShareDialogOnlyByLink'
8
- import { SharedDriveEditModal } from './SharedDrive/SharedDriveEditModal'
9
8
 
10
9
  export const ShareModal = ({
11
10
  autoOpenShareRestriction,
@@ -15,7 +14,6 @@ export const ShareModal = ({
15
14
  hasSharedChild,
16
15
  hasSharedParent,
17
16
  isOwner,
18
- isSharedDrive,
19
17
  link,
20
18
  onClose,
21
19
  onRevoke,
@@ -28,21 +26,6 @@ export const ShareModal = ({
28
26
  showGenerateLinkButton,
29
27
  twoStepsConfirmationMethods
30
28
  }) => {
31
- if (isSharedDrive && !flag('drive.federated-shared-folder.enabled')) {
32
- return (
33
- <SharedDriveEditModal
34
- document={document}
35
- sharing={sharing}
36
- recipients={recipients}
37
- onShare={onShare}
38
- onRevoke={onRevoke}
39
- onClose={onClose}
40
- autoOpenShareRestriction={autoOpenShareRestriction}
41
- showGenerateLinkButton={showGenerateLinkButton}
42
- />
43
- )
44
- }
45
-
46
29
  const shareDialogOnlyByLink =
47
30
  flag('cozy.hide-sharing-cozy-to-cozy') || documentType === 'Albums'
48
31
 
@@ -0,0 +1,81 @@
1
+ import PropTypes from 'prop-types'
2
+ import React from 'react'
3
+
4
+ import Button from 'cozy-ui/transpiled/react/Buttons'
5
+ import TextField from 'cozy-ui/transpiled/react/TextField'
6
+ import Typography from 'cozy-ui/transpiled/react/Typography'
7
+ import { useI18n } from 'twake-i18n'
8
+
9
+ import withLocales from '../../hoc/withLocales'
10
+ import { default as DumbShareByEmail } from '../ShareByEmail'
11
+ import WhoHasAccess from '../WhoHasAccess'
12
+ import { useSharedDrive } from './useSharedDrive'
13
+
14
+ export const SharedDriveForm = withLocales(({ onSuccess, onCancel }) => {
15
+ const { t } = useI18n()
16
+ const {
17
+ sharedDriveName,
18
+ recipients,
19
+ handleSharedDriveNameChange,
20
+ onShare,
21
+ onCreate,
22
+ onSetType,
23
+ onRevoke,
24
+ createContact
25
+ } = useSharedDrive({ onSuccess })
26
+
27
+ return (
28
+ <div>
29
+ <div className="u-ph-2">
30
+ <TextField
31
+ required
32
+ label={t('SharedDrive.sharedDriveModal.nameLabel')}
33
+ variant="outlined"
34
+ size="small"
35
+ className="u-w-100 u-mt-1-half"
36
+ value={sharedDriveName}
37
+ onChange={handleSharedDriveNameChange}
38
+ />
39
+ <Typography variant="h6" className="u-mt-1-half u-mb-half">
40
+ {t('SharedDrive.sharedDriveModal.addPeople')}
41
+ </Typography>
42
+ <DumbShareByEmail
43
+ createContact={createContact}
44
+ currentRecipients={recipients}
45
+ documentType="Files"
46
+ sharedDrive
47
+ onShare={onShare}
48
+ submitLabel={t('SharedDrive.sharedDriveModal.add')}
49
+ showNotifications={false}
50
+ sharingDesc=""
51
+ />
52
+ </div>
53
+ <WhoHasAccess
54
+ isOwner
55
+ isSharedDrive
56
+ recipients={recipients}
57
+ documentType="Files"
58
+ className="u-w-100"
59
+ onRevoke={onRevoke}
60
+ onSetType={onSetType}
61
+ />
62
+ <div className="u-flex u-flex-justify-end u-ph-2 u-pv-1 u-column-gap-half">
63
+ <Button
64
+ variant="secondary"
65
+ label={t('SharedDrive.sharedDriveModal.cancel')}
66
+ onClick={onCancel}
67
+ />
68
+ <Button
69
+ variant="primary"
70
+ label={t('SharedDrive.sharedDriveModal.create')}
71
+ onClick={onCreate}
72
+ />
73
+ </div>
74
+ </div>
75
+ )
76
+ })
77
+
78
+ SharedDriveForm.propTypes = {
79
+ onSuccess: PropTypes.func.isRequired,
80
+ onCancel: PropTypes.func.isRequired
81
+ }
@@ -1,141 +1,25 @@
1
1
  import PropTypes from 'prop-types'
2
- import React, { useState } from 'react'
2
+ import React from 'react'
3
3
 
4
- import { useClient } from 'cozy-client'
5
- import { useAlert } from 'cozy-ui/transpiled/react/providers/Alert'
4
+ import { FixedDialog } from 'cozy-ui/transpiled/react/CozyDialogs'
6
5
  import { useI18n } from 'twake-i18n'
7
6
 
8
- import { DumbSharedDriveModal } from './DumbSharedDriveModal'
9
- import {
10
- mergeAndDeduplicateRecipients,
11
- formatRecipients,
12
- RECIPIENT_INDEX_PREFIX
13
- } from './helpers'
14
7
  import withLocales from '../../hoc/withLocales'
15
- import { useSharingContext } from '../../hooks/useSharingContext'
16
- import { Contact } from '../../models'
8
+ import { SharedDriveForm } from './SharedDriveForm'
17
9
 
18
10
  export const SharedDriveModal = withLocales(({ onClose }) => {
19
- const client = useClient()
20
11
  const { t } = useI18n()
21
- const { share } = useSharingContext()
22
- const { showAlert } = useAlert()
23
-
24
- const [sharedDriveRecipients, setSharedDriveRecipients] = useState({
25
- recipients: [],
26
- readOnlyRecipients: []
27
- })
28
- const [sharedDriveName, setSharedDriveName] = useState('')
29
-
30
- const onShare = params => {
31
- setSharedDriveRecipients({
32
- recipients: mergeAndDeduplicateRecipients([
33
- sharedDriveRecipients.recipients,
34
- params.recipients
35
- ]),
36
- readOnlyRecipients: mergeAndDeduplicateRecipients([
37
- sharedDriveRecipients.readOnlyRecipients,
38
- params.readOnlyRecipients
39
- ])
40
- })
41
- }
42
-
43
- const handleSharedDriveNameChange = event => {
44
- setSharedDriveName(event.target.value)
45
- }
46
-
47
- const onCreate = async () => {
48
- try {
49
- await client
50
- .collection('io.cozy.files')
51
- .getOrCreateSharedDrivesDirectory()
52
- const { data: sharedDriveFolder } = await client.create('io.cozy.files', {
53
- name: sharedDriveName,
54
- dirId: 'io.cozy.files.shared-drives-dir',
55
- type: 'directory'
56
- })
57
-
58
- await share({
59
- description: sharedDriveName,
60
- document: sharedDriveFolder,
61
- recipients: sharedDriveRecipients.recipients,
62
- readOnlyRecipients: sharedDriveRecipients.readOnlyRecipients,
63
- sharedDrive: true,
64
- openSharing: false
65
- })
66
-
67
- showAlert({
68
- message: t('SharedDrive.sharedDriveModal.successNotification'),
69
- severity: 'success',
70
- variant: 'filled'
71
- })
72
-
73
- onClose()
74
- } catch (_err) {
75
- showAlert({
76
- message: t('SharedDrive.sharedDriveModal.errorNotification'),
77
- severity: 'error',
78
- variant: 'filled'
79
- })
80
- }
81
- }
82
-
83
- const onSetType = (index, newType) => {
84
- const _id = index.split(RECIPIENT_INDEX_PREFIX)[1]
85
-
86
- if (newType === 'two-way') {
87
- setSharedDriveRecipients(prev => {
88
- const recipientToMove = prev.readOnlyRecipients.find(r => r._id === _id)
89
-
90
- return {
91
- recipients: [...prev.recipients, recipientToMove],
92
- readOnlyRecipients: prev.readOnlyRecipients.filter(r => r._id !== _id)
93
- }
94
- })
95
- } else {
96
- setSharedDriveRecipients(prev => {
97
- const recipientToMove = prev.recipients.find(r => r._id === _id)
98
-
99
- return {
100
- recipients: prev.recipients.filter(r => r._id !== _id),
101
- readOnlyRecipients: [...prev.readOnlyRecipients, recipientToMove]
102
- }
103
- })
104
- }
105
- }
106
-
107
- const onRevoke = index => {
108
- const _id = index.split(RECIPIENT_INDEX_PREFIX)[1]
109
-
110
- setSharedDriveRecipients(prev => {
111
- return {
112
- recipients: prev.recipients.filter(r => r._id !== _id),
113
- readOnlyRecipients: prev.readOnlyRecipients.filter(r => r._id !== _id)
114
- }
115
- })
116
- }
117
-
118
- const createContact = contact => client.create(Contact.doctype, contact)
119
-
120
- const recipients = formatRecipients(sharedDriveRecipients)
121
-
122
12
  return (
123
- <DumbSharedDriveModal
124
- title={t('SharedDrive.sharedDriveModal.title')}
125
- sharedDriveName={sharedDriveName}
126
- handleSharedDriveNameChange={handleSharedDriveNameChange}
127
- createContact={createContact}
128
- recipients={recipients}
129
- currentRecipients={[]}
130
- onRevoke={onRevoke}
131
- onSetType={onSetType}
132
- onCreate={onCreate}
133
- onSend={undefined}
13
+ <FixedDialog
14
+ open
15
+ disableGutters
134
16
  onClose={onClose}
135
- onShare={onShare}
136
- showNameField={true}
137
- sharingLink={undefined}
138
- document={undefined}
17
+ title={t('SharedDrive.sharedDriveModal.title')}
18
+ classes={{ paper: 'u-ov-visible' }}
19
+ componentsProps={{
20
+ dialogContent: { className: 'u-ov-visible' }
21
+ }}
22
+ content={<SharedDriveForm onSuccess={onClose} onCancel={onClose} />}
139
23
  />
140
24
  )
141
25
  })
@@ -73,7 +73,7 @@ export const formatRecipients = sharedDriveRecipients => {
73
73
  recipients.push({
74
74
  index: `${RECIPIENT_INDEX_PREFIX}${r._id}`,
75
75
  email: ContactModel.getPrimaryEmail(r),
76
- public_name: r.displayName || r.name,
76
+ public_name: ContactModel.getDisplayName(r),
77
77
  memberIndex: r._id,
78
78
  type: 'two-way',
79
79
  members: r.members
@@ -84,7 +84,7 @@ export const formatRecipients = sharedDriveRecipients => {
84
84
  recipients.push({
85
85
  index: `${RECIPIENT_INDEX_PREFIX}${r._id}`,
86
86
  email: ContactModel.getPrimaryEmail(r),
87
- public_name: r.displayName || r.name,
87
+ public_name: ContactModel.getDisplayName(r),
88
88
  memberIndex: r._id,
89
89
  type: 'one-way',
90
90
  members: r.members
@@ -138,6 +138,22 @@ describe('formatRecipients', () => {
138
138
  })
139
139
  })
140
140
 
141
+ it('should flatten an object-shaped contact name into a string public_name', () => {
142
+ const input = {
143
+ recipients: [
144
+ {
145
+ _id: '3',
146
+ name: { givenName: 'Arya', familyName: 'Stark' },
147
+ email: [{ address: 'arya@example.com', primary: true }]
148
+ }
149
+ ],
150
+ readOnlyRecipients: []
151
+ }
152
+ const result = formatRecipients(input)
153
+ expect(typeof result[0].public_name).toBe('string')
154
+ expect(result[0].public_name).toBe('Arya Stark')
155
+ })
156
+
141
157
  it('should sort recipients by public_name', () => {
142
158
  const input = {
143
159
  recipients: [