cozy-sharing 32.1.2 → 32.3.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/.claude/settings.local.json +27 -0
- package/AGENTS.md +7 -0
- package/CHANGELOG.md +18 -0
- package/dist/RefreshableSharings.spec.js +40 -0
- package/dist/assets/illustrations/illustration-shared-drives.svg +1 -0
- package/dist/components/EditLinkPermissionDialog.js +72 -0
- package/dist/components/EditLinkPermissionDialog.spec.js +48 -0
- package/dist/components/FederatedFolder/DumbFederatedFolderModal.js +65 -0
- package/dist/components/FederatedFolder/DumbFederatedFolderModal.spec.js +107 -0
- package/dist/components/FederatedFolder/FederatedFolderModal.js +41 -4
- package/dist/components/FederatedFolder/FederatedFolderModal.jsx.tmp.179197.1773939270471 +172 -0
- package/dist/components/FederatedFolder/FederatedFolderModal.spec.js +79 -8
- package/dist/components/Recipient/actions/revokeGroup.js +42 -0
- package/dist/components/Recipient/actions/revokeMember.js +42 -0
- package/dist/components/Recipient/actions/revokeSharedDriveMember.js +41 -0
- package/dist/components/ShareAutosuggest.js +1 -0
- package/dist/components/ShareAutosuggest.spec.js +12 -4
- package/dist/components/ShareDialogCozyToCozy.spec.js +55 -8
- package/dist/components/ShareDiscardChangesModal.js +35 -0
- package/dist/components/ShareModal/ShareModal copy.js +43 -0
- package/dist/components/ShareModal/ShareModal.spec.js +197 -0
- package/dist/components/ShareModal/ShareModalBatchContainer.js +276 -0
- package/dist/components/ShareModal/ShareModalBatchContainer.spec.js +131 -0
- package/dist/components/ShareModal/SharedDriveEditModal.js +59 -0
- package/dist/components/ShareRecipientsInput.js +13 -3
- package/dist/components/ShareRecipientsInput.spec.js +63 -4
- package/dist/components/SharedDrive/DumbSharedDriveModal.js +78 -0
- package/dist/components/SharedDrive/DumbSharedDriveModal.spec.js +126 -0
- package/dist/components/SharedDrive/SharedDriveEditModal.js +110 -0
- package/dist/components/SharedDrive/SharedDriveModal copy.js +356 -0
- package/dist/components/SharedDrive/helpers.js +102 -0
- package/dist/components/SharedDrive/helpers.spec.js +208 -0
- package/dist/components/SharedDriveEditModal.js +43 -0
- package/dist/components/SharedFolder/DumbBatchSharedFolderModal.spec.js +321 -0
- package/dist/components/SharedStatus.js +61 -0
- package/dist/components/SharedStatus.spec.js +44 -0
- package/dist/components/Sharesubmit.js +28 -0
- package/dist/components/__snapshots__/SharedStatus.spec.js.snap +94 -0
- package/dist/helpers/owner.js +14 -0
- package/dist/helpers/owner.spec.js +34 -0
- package/dist/hooks/useConfirmDiscardChanges.js +33 -0
- package/dist/hooks/useContactsAndGroups.js +65 -0
- package/dist/hooks/useSharedDrive.js +130 -0
- package/dist/hooks/useSharedDriveContactsAndGroups.js +65 -0
- package/dist/propTypes.js +26 -0
- package/dist/styles/shareddrive.styl +8 -0
- package/locales/en.json +10 -5
- package/locales/fr.json +10 -5
- package/locales/ru.json +10 -5
- package/locales/vi.json +10 -5
- package/package.json +2 -2
- package/src/components/FederatedFolder/FederatedFolderModal.jsx +184 -138
- package/src/components/FederatedFolder/FederatedFolderModal.spec.jsx +47 -0
- package/src/components/ShareAutosuggest.jsx +1 -0
- package/src/components/ShareAutosuggest.spec.jsx +10 -5
- package/src/components/ShareDialogCozyToCozy.spec.jsx +50 -4
- package/src/components/ShareDiscardChangesModal.jsx +40 -0
- package/src/components/ShareRecipientsInput.jsx +11 -6
- package/src/components/ShareRecipientsInput.spec.jsx +59 -0
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import PropTypes from 'prop-types'
|
|
2
|
-
import React, { useEffect, useState } from 'react'
|
|
2
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
|
3
3
|
|
|
4
4
|
import { useClient } from 'cozy-client'
|
|
5
|
+
import Button from 'cozy-ui/transpiled/react/Buttons'
|
|
5
6
|
import { useAlert } from 'cozy-ui/transpiled/react/providers/Alert'
|
|
7
|
+
import ConfirmDialogProvider, {
|
|
8
|
+
useConfirmDialog
|
|
9
|
+
} from 'cozy-ui/transpiled/react/providers/ConfirmDialog'
|
|
6
10
|
import { useI18n } from 'twake-i18n'
|
|
7
11
|
|
|
8
12
|
import { getOrCreateFromArray } from '../../helpers/contacts'
|
|
@@ -11,149 +15,191 @@ import { usePendingRecipients } from '../../hooks/usePendingRecipients'
|
|
|
11
15
|
import { useSharingContext } from '../../hooks/useSharingContext'
|
|
12
16
|
import { DumbBatchSharedFolderModal } from '../SharedFolder/DumbBatchSharedFolderModal'
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const fetchSharingLink = async () => {
|
|
59
|
-
if (!existingDocument) return
|
|
60
|
-
|
|
61
|
-
if (existingDocument.driveId) {
|
|
62
|
-
const link = await getFederatedShareLink(existingDocument)
|
|
63
|
-
setSharingLink(link)
|
|
64
|
-
} else {
|
|
65
|
-
setSharingLink(getSharingLink(existingDocument._id))
|
|
66
|
-
}
|
|
67
|
-
}
|
|
18
|
+
const FederatedFolderModalContent = ({
|
|
19
|
+
onClose,
|
|
20
|
+
document: existingDocument,
|
|
21
|
+
autoOpenShareRestriction,
|
|
22
|
+
showGenerateLinkButton
|
|
23
|
+
}) => {
|
|
24
|
+
const client = useClient()
|
|
25
|
+
const { t } = useI18n()
|
|
26
|
+
const {
|
|
27
|
+
share,
|
|
28
|
+
getSharingLink,
|
|
29
|
+
getFederatedShareLink,
|
|
30
|
+
getDocumentPermissions,
|
|
31
|
+
getOwner,
|
|
32
|
+
getSharingById,
|
|
33
|
+
getRecipients,
|
|
34
|
+
revoke
|
|
35
|
+
} = useSharingContext()
|
|
36
|
+
const { showAlert } = useAlert()
|
|
37
|
+
const { showConfirmDialog, closeConfirmDialog } = useConfirmDialog()
|
|
38
|
+
|
|
39
|
+
const [sharingLink, setSharingLink] = useState(null)
|
|
40
|
+
const {
|
|
41
|
+
pendingRecipients,
|
|
42
|
+
setPendingRecipients,
|
|
43
|
+
selectedOption,
|
|
44
|
+
setSelectedOption
|
|
45
|
+
} = usePendingRecipients()
|
|
46
|
+
|
|
47
|
+
// share from CozyProvider is wired to callbacks and realtime that
|
|
48
|
+
// makes existingDocument and existingRecipients reactive to changes.
|
|
49
|
+
// But we want it to be reactive only when we revoke a member. When we add member
|
|
50
|
+
// we close the popup and we do not want to see briefly the new contacts added
|
|
51
|
+
// in members before the modal closes. That's why when clicking on "Share"
|
|
52
|
+
// we do not use the reactive existingDocument and existingRecipients.
|
|
53
|
+
const [isSending, setIsSending] = useState(false)
|
|
54
|
+
const [frozenDoc, setFrozenDoc] = useState(null)
|
|
55
|
+
const [frozenRecipients, setFrozenRecipients] = useState(null)
|
|
56
|
+
|
|
57
|
+
const handleCloseRequest = useCallback(() => {
|
|
58
|
+
if (pendingRecipients.length === 0) {
|
|
59
|
+
onClose()
|
|
60
|
+
return
|
|
61
|
+
}
|
|
68
62
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
63
|
+
showConfirmDialog({
|
|
64
|
+
title: t('ShareDiscardChangesModal.title'),
|
|
65
|
+
actions: (
|
|
66
|
+
<>
|
|
67
|
+
<Button
|
|
68
|
+
variant="secondary"
|
|
69
|
+
label={t('ShareDiscardChangesModal.cancel')}
|
|
70
|
+
className="u-fz-small"
|
|
71
|
+
onClick={closeConfirmDialog}
|
|
72
|
+
/>
|
|
73
|
+
<Button
|
|
74
|
+
variant="primary"
|
|
75
|
+
label={t('ShareDiscardChangesModal.discard')}
|
|
76
|
+
className="u-fz-small"
|
|
77
|
+
onClick={() => {
|
|
78
|
+
closeConfirmDialog()
|
|
79
|
+
onClose()
|
|
80
|
+
}}
|
|
81
|
+
/>
|
|
82
|
+
</>
|
|
83
|
+
)
|
|
84
|
+
})
|
|
85
|
+
}, [
|
|
86
|
+
closeConfirmDialog,
|
|
87
|
+
onClose,
|
|
88
|
+
pendingRecipients.length,
|
|
89
|
+
showConfirmDialog,
|
|
90
|
+
t
|
|
91
|
+
])
|
|
92
|
+
|
|
93
|
+
const documentPermissions = useMemo(
|
|
94
|
+
() =>
|
|
95
|
+
existingDocument ? getDocumentPermissions(existingDocument._id) : [],
|
|
96
|
+
[existingDocument, getDocumentPermissions]
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
const fetchSharingLink = async () => {
|
|
101
|
+
if (!existingDocument) return
|
|
102
|
+
|
|
103
|
+
if (existingDocument.driveId) {
|
|
104
|
+
const link = await getFederatedShareLink(existingDocument)
|
|
105
|
+
setSharingLink(link)
|
|
106
|
+
} else {
|
|
107
|
+
setSharingLink(getSharingLink(existingDocument._id))
|
|
85
108
|
}
|
|
109
|
+
}
|
|
86
110
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
document: existingDocument,
|
|
104
|
-
recipients: readWriteRecipients,
|
|
105
|
-
readOnlyRecipients,
|
|
106
|
-
sharedDrive: true,
|
|
107
|
-
openSharing: false
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
showAlert({
|
|
111
|
-
message: t('FederatedFolder.successNotification'),
|
|
112
|
-
severity: 'success',
|
|
113
|
-
variant: 'filled'
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
onClose()
|
|
117
|
-
} catch (_err) {
|
|
118
|
-
showAlert({
|
|
119
|
-
message: t('FederatedFolder.errorNotification'),
|
|
120
|
-
severity: 'error',
|
|
121
|
-
variant: 'filled'
|
|
122
|
-
})
|
|
123
|
-
} finally {
|
|
124
|
-
setIsSending(false)
|
|
125
|
-
}
|
|
111
|
+
fetchSharingLink()
|
|
112
|
+
}, [
|
|
113
|
+
existingDocument,
|
|
114
|
+
documentPermissions,
|
|
115
|
+
getFederatedShareLink,
|
|
116
|
+
getSharingLink,
|
|
117
|
+
getOwner,
|
|
118
|
+
getSharingById
|
|
119
|
+
])
|
|
120
|
+
|
|
121
|
+
const folderName = existingDocument?.name || ''
|
|
122
|
+
|
|
123
|
+
const onSend = async () => {
|
|
124
|
+
if (pendingRecipients.length === 0) {
|
|
125
|
+
onClose()
|
|
126
|
+
return
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
129
|
+
setIsSending(true)
|
|
130
|
+
setFrozenDoc(existingDocument)
|
|
131
|
+
setFrozenRecipients(existingRecipients)
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const contacts = await getOrCreateFromArray(
|
|
135
|
+
client,
|
|
136
|
+
pendingRecipients,
|
|
137
|
+
contact => client.create('io.cozy.contacts', contact)
|
|
138
|
+
)
|
|
139
|
+
const readWriteRecipients = selectedOption === 'readOnly' ? [] : contacts
|
|
140
|
+
const readOnlyRecipients = selectedOption === 'readOnly' ? contacts : []
|
|
141
|
+
|
|
142
|
+
await share({
|
|
143
|
+
description: folderName,
|
|
144
|
+
document: existingDocument,
|
|
145
|
+
recipients: readWriteRecipients,
|
|
146
|
+
readOnlyRecipients,
|
|
147
|
+
sharedDrive: true,
|
|
148
|
+
openSharing: false
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
showAlert({
|
|
152
|
+
message: t('FederatedFolder.successNotification'),
|
|
153
|
+
severity: 'success',
|
|
154
|
+
variant: 'filled'
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
onClose()
|
|
158
|
+
} catch (_err) {
|
|
159
|
+
showAlert({
|
|
160
|
+
message: t('FederatedFolder.errorNotification'),
|
|
161
|
+
severity: 'error',
|
|
162
|
+
variant: 'filled'
|
|
163
|
+
})
|
|
164
|
+
} finally {
|
|
165
|
+
setIsSending(false)
|
|
166
|
+
}
|
|
155
167
|
}
|
|
156
|
-
|
|
168
|
+
|
|
169
|
+
const existingRecipients = existingDocument
|
|
170
|
+
? getRecipients(existingDocument._id)
|
|
171
|
+
: []
|
|
172
|
+
|
|
173
|
+
const modalTitle = t('FederatedFolder.shareTitle', { name: folderName })
|
|
174
|
+
const isSharedDrive = Boolean(existingDocument?.driveId)
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<DumbBatchSharedFolderModal
|
|
178
|
+
title={modalTitle}
|
|
179
|
+
document={isSending ? frozenDoc : existingDocument}
|
|
180
|
+
recipients={isSending ? frozenRecipients : existingRecipients}
|
|
181
|
+
currentRecipients={isSending ? frozenRecipients : existingRecipients}
|
|
182
|
+
onRevoke={revoke}
|
|
183
|
+
onSend={onSend}
|
|
184
|
+
onClose={handleCloseRequest}
|
|
185
|
+
sharingLink={sharingLink}
|
|
186
|
+
shareLabel={t('FederatedFolder.share')}
|
|
187
|
+
showShareByEmail={!isSharedDrive}
|
|
188
|
+
autoOpenShareRestriction={autoOpenShareRestriction}
|
|
189
|
+
showGenerateLinkButton={showGenerateLinkButton}
|
|
190
|
+
pendingRecipients={pendingRecipients}
|
|
191
|
+
onPendingRecipientsChange={setPendingRecipients}
|
|
192
|
+
selectedOption={selectedOption}
|
|
193
|
+
onSelectedOptionChange={setSelectedOption}
|
|
194
|
+
/>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const FederatedFolderModal = withLocales(props => (
|
|
199
|
+
<ConfirmDialogProvider>
|
|
200
|
+
<FederatedFolderModalContent {...props} />
|
|
201
|
+
</ConfirmDialogProvider>
|
|
202
|
+
))
|
|
157
203
|
|
|
158
204
|
FederatedFolderModal.propTypes = {
|
|
159
205
|
onClose: PropTypes.func.isRequired,
|
|
@@ -320,6 +320,53 @@ describe('FederatedFolderModal', () => {
|
|
|
320
320
|
|
|
321
321
|
expect(mockOnClose).toHaveBeenCalled()
|
|
322
322
|
})
|
|
323
|
+
|
|
324
|
+
it('should ask confirmation when close button is clicked with pending recipients', async () => {
|
|
325
|
+
usePendingRecipients.mockReturnValue({
|
|
326
|
+
pendingRecipients: [{ name: 'Alice', email: 'alice@example.com' }],
|
|
327
|
+
setPendingRecipients: jest.fn(),
|
|
328
|
+
selectedOption: 'readWrite',
|
|
329
|
+
setSelectedOption: jest.fn()
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
const { getByRole, getByTestId, getByText } = setup()
|
|
333
|
+
|
|
334
|
+
await waitFor(() => {
|
|
335
|
+
expect(getByTestId('btn-close')).toBeTruthy()
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
fireEvent.click(getByTestId('btn-close'))
|
|
339
|
+
|
|
340
|
+
expect(getByText("Discard the changes you haven't saved?")).toBeTruthy()
|
|
341
|
+
expect(mockOnClose).not.toHaveBeenCalled()
|
|
342
|
+
|
|
343
|
+
fireEvent.click(getByRole('button', { name: 'Cancel' }))
|
|
344
|
+
|
|
345
|
+
expect(mockOnClose).not.toHaveBeenCalled()
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
it('should close after discard confirmation', async () => {
|
|
349
|
+
usePendingRecipients.mockReturnValue({
|
|
350
|
+
pendingRecipients: [{ name: 'Alice', email: 'alice@example.com' }],
|
|
351
|
+
setPendingRecipients: jest.fn(),
|
|
352
|
+
selectedOption: 'readWrite',
|
|
353
|
+
setSelectedOption: jest.fn()
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
const { getByRole, getByTestId, getByText } = setup()
|
|
357
|
+
|
|
358
|
+
await waitFor(() => {
|
|
359
|
+
expect(getByTestId('btn-close')).toBeTruthy()
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
fireEvent.click(getByTestId('btn-close'))
|
|
363
|
+
|
|
364
|
+
expect(getByText("Discard the changes you haven't saved?")).toBeTruthy()
|
|
365
|
+
|
|
366
|
+
fireEvent.click(getByRole('button', { name: 'Discard' }))
|
|
367
|
+
|
|
368
|
+
expect(mockOnClose).toHaveBeenCalledTimes(1)
|
|
369
|
+
})
|
|
323
370
|
})
|
|
324
371
|
|
|
325
372
|
describe('existing recipients', () => {
|
|
@@ -51,17 +51,22 @@ describe('ShareAutosuggest', () => {
|
|
|
51
51
|
})
|
|
52
52
|
})
|
|
53
53
|
|
|
54
|
-
it('should show loading
|
|
54
|
+
it('should show loading indicator when loading prop is true (autoFocus triggers onFocus on mount)', () => {
|
|
55
55
|
const onPick = jest.fn()
|
|
56
56
|
const onRemove = jest.fn()
|
|
57
57
|
|
|
58
58
|
setup({ onPick, onRemove, loading: true })
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
// autoFocus triggers onFocus on mount, which sets isLoadingDisplayed
|
|
61
|
+
expect(screen.getByText('loading')).toBeInTheDocument()
|
|
62
|
+
})
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
it('should not show loading indicator when loading prop is false', () => {
|
|
65
|
+
const onPick = jest.fn()
|
|
66
|
+
const onRemove = jest.fn()
|
|
64
67
|
|
|
65
|
-
|
|
68
|
+
setup({ onPick, onRemove, loading: false })
|
|
69
|
+
|
|
70
|
+
expect(screen.queryByText('loading')).toBeNull()
|
|
66
71
|
})
|
|
67
72
|
})
|
|
@@ -169,20 +169,66 @@ describe('ShareDialogCozyToCozy', () => {
|
|
|
169
169
|
).not.toBeInTheDocument()
|
|
170
170
|
})
|
|
171
171
|
|
|
172
|
-
it('main
|
|
172
|
+
it('main Done button calls onClose when there are no pending chips', () => {
|
|
173
173
|
const onClose = jest.fn()
|
|
174
174
|
const onShare = jest.fn()
|
|
175
175
|
const props = { ...getMockProps(), onClose, onShare }
|
|
176
176
|
|
|
177
177
|
const { getByRole } = setup(props)
|
|
178
178
|
|
|
179
|
-
fireEvent.click(getByRole('button', { name: '
|
|
179
|
+
fireEvent.click(getByRole('button', { name: 'Done' }))
|
|
180
180
|
|
|
181
181
|
expect(onShare).not.toHaveBeenCalled()
|
|
182
182
|
expect(onClose).toHaveBeenCalledTimes(1)
|
|
183
183
|
})
|
|
184
184
|
|
|
185
|
-
it('
|
|
185
|
+
it('close action closes dialog when there are no pending chips', () => {
|
|
186
|
+
const onClose = jest.fn()
|
|
187
|
+
const props = { ...getMockProps(), onClose }
|
|
188
|
+
|
|
189
|
+
const { getByRole } = setup(props)
|
|
190
|
+
|
|
191
|
+
fireEvent.click(getByRole('button', { name: 'Close' }))
|
|
192
|
+
|
|
193
|
+
expect(onClose).toHaveBeenCalledTimes(1)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('close action closes dialog when there are pending chips', () => {
|
|
197
|
+
const onClose = jest.fn()
|
|
198
|
+
const props = { ...getMockProps(), onClose }
|
|
199
|
+
|
|
200
|
+
const { getByPlaceholderText, getByRole, queryByText } = setup(props)
|
|
201
|
+
|
|
202
|
+
act(() => {
|
|
203
|
+
fireEvent.change(
|
|
204
|
+
getByPlaceholderText(
|
|
205
|
+
'Enter the email address or name of the recipient'
|
|
206
|
+
),
|
|
207
|
+
{
|
|
208
|
+
target: { value: 'new@cozycloud.cc' }
|
|
209
|
+
}
|
|
210
|
+
)
|
|
211
|
+
})
|
|
212
|
+
act(() => {
|
|
213
|
+
fireEvent.keyPress(
|
|
214
|
+
getByPlaceholderText(
|
|
215
|
+
'Enter the email address or name of the recipient'
|
|
216
|
+
),
|
|
217
|
+
{
|
|
218
|
+
key: 'Enter',
|
|
219
|
+
code: 'Enter',
|
|
220
|
+
charCode: 13
|
|
221
|
+
}
|
|
222
|
+
)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
fireEvent.click(getByRole('button', { name: 'Close' }))
|
|
226
|
+
|
|
227
|
+
expect(queryByText("Discard the changes you haven't saved?")).toBe(null)
|
|
228
|
+
expect(onClose).toHaveBeenCalledTimes(1)
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('main Done button calls onShare with chips then closes', async () => {
|
|
186
232
|
const onClose = jest.fn()
|
|
187
233
|
const onShare = jest.fn().mockResolvedValue(undefined)
|
|
188
234
|
const props = { ...getMockProps(), onClose, onShare }
|
|
@@ -213,7 +259,7 @@ describe('ShareDialogCozyToCozy', () => {
|
|
|
213
259
|
})
|
|
214
260
|
|
|
215
261
|
await act(async () => {
|
|
216
|
-
fireEvent.click(getByRole('button', { name: '
|
|
262
|
+
fireEvent.click(getByRole('button', { name: 'Done' }))
|
|
217
263
|
})
|
|
218
264
|
|
|
219
265
|
expect(onShare).toHaveBeenCalledWith(
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import PropTypes from 'prop-types'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
import Button from 'cozy-ui/transpiled/react/Buttons'
|
|
5
|
+
import { ConfirmDialog } from 'cozy-ui/transpiled/react/CozyDialogs'
|
|
6
|
+
import { useI18n } from 'twake-i18n'
|
|
7
|
+
|
|
8
|
+
const ShareDiscardChangesModal = ({ onCancel, onConfirm }) => {
|
|
9
|
+
const { t } = useI18n()
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<ConfirmDialog
|
|
13
|
+
open
|
|
14
|
+
title={t('ShareDiscardChangesModal.title')}
|
|
15
|
+
actions={
|
|
16
|
+
<>
|
|
17
|
+
<Button
|
|
18
|
+
variant="secondary"
|
|
19
|
+
label={t('ShareDiscardChangesModal.cancel')}
|
|
20
|
+
className="u-fz-small"
|
|
21
|
+
onClick={onCancel}
|
|
22
|
+
/>
|
|
23
|
+
<Button
|
|
24
|
+
variant="primary"
|
|
25
|
+
label={t('ShareDiscardChangesModal.discard')}
|
|
26
|
+
className="u-fz-small"
|
|
27
|
+
onClick={onConfirm}
|
|
28
|
+
/>
|
|
29
|
+
</>
|
|
30
|
+
}
|
|
31
|
+
/>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
ShareDiscardChangesModal.propTypes = {
|
|
36
|
+
onCancel: PropTypes.func.isRequired,
|
|
37
|
+
onConfirm: PropTypes.func.isRequired
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { ShareDiscardChangesModal }
|
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
* We retrieve all the contacts that are reachable and the groups they belong to.
|
|
18
18
|
* In order to display the total number of members in each group, we also retrieve the contacts that are not reachable.
|
|
19
19
|
*/
|
|
20
|
+
const isCurrentUser = contact => contact.me === true
|
|
21
|
+
|
|
20
22
|
const ShareRecipientsInput = ({
|
|
21
23
|
currentRecipients,
|
|
22
24
|
recipients,
|
|
@@ -57,6 +59,12 @@ const ShareRecipientsInput = ({
|
|
|
57
59
|
(isQueryLoading(unreachableContactsWithGroupsResult) ||
|
|
58
60
|
unreachableContactsWithGroupsResult.hasMore))
|
|
59
61
|
|
|
62
|
+
const reachableContacts = (reachableContactsResult.data || []).filter(
|
|
63
|
+
contact => !isCurrentUser(contact)
|
|
64
|
+
)
|
|
65
|
+
const unreachableContactsWithGroups = (
|
|
66
|
+
unreachableContactsWithGroupsResult.data || []
|
|
67
|
+
).filter(contact => !isCurrentUser(contact))
|
|
60
68
|
const currentRecipientGroups = currentRecipients
|
|
61
69
|
.map(({ id }) => id)
|
|
62
70
|
.filter(Boolean)
|
|
@@ -68,7 +76,7 @@ const ShareRecipientsInput = ({
|
|
|
68
76
|
)
|
|
69
77
|
.map(contactGroup => {
|
|
70
78
|
const reachableMembers = getContactsFromGroupId(
|
|
71
|
-
|
|
79
|
+
reachableContacts,
|
|
72
80
|
contactGroup.id
|
|
73
81
|
).map(contact => ({
|
|
74
82
|
...contact,
|
|
@@ -83,7 +91,7 @@ const ShareRecipientsInput = ({
|
|
|
83
91
|
}
|
|
84
92
|
|
|
85
93
|
const unreachableMembers = getContactsFromGroupId(
|
|
86
|
-
|
|
94
|
+
unreachableContactsWithGroups,
|
|
87
95
|
contactGroup.id
|
|
88
96
|
).map(contact => ({
|
|
89
97
|
...contact,
|
|
@@ -96,10 +104,7 @@ const ShareRecipientsInput = ({
|
|
|
96
104
|
}
|
|
97
105
|
})
|
|
98
106
|
|
|
99
|
-
const contactsAndGroups = [
|
|
100
|
-
...(reachableContactsResult.data || []),
|
|
101
|
-
...contactGroups
|
|
102
|
-
]
|
|
107
|
+
const contactsAndGroups = [...reachableContacts, ...contactGroups]
|
|
103
108
|
|
|
104
109
|
return (
|
|
105
110
|
<ShareAutosuggest
|
|
@@ -116,6 +116,65 @@ describe('ShareRecipientsInput component', () => {
|
|
|
116
116
|
).toBeInTheDocument()
|
|
117
117
|
})
|
|
118
118
|
|
|
119
|
+
it('should exclude current user from suggestions when me is true', async () => {
|
|
120
|
+
const contacts = [
|
|
121
|
+
{
|
|
122
|
+
id: 'df563cc4-6440',
|
|
123
|
+
_id: 'df563cc4-6440',
|
|
124
|
+
_type: 'io.cozy.contacts',
|
|
125
|
+
me: true,
|
|
126
|
+
name: {
|
|
127
|
+
givenName: 'My',
|
|
128
|
+
familyName: 'Self'
|
|
129
|
+
},
|
|
130
|
+
relationships: {
|
|
131
|
+
groups: {
|
|
132
|
+
data: [
|
|
133
|
+
{
|
|
134
|
+
_id: 'group-id',
|
|
135
|
+
_type: 'io.cozy.contacts.groups'
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: '5a3b4ccf-c257',
|
|
143
|
+
_id: '5a3b4ccf-c257',
|
|
144
|
+
_type: 'io.cozy.contacts',
|
|
145
|
+
name: {
|
|
146
|
+
givenName: 'Teagan',
|
|
147
|
+
familyName: 'Wolf'
|
|
148
|
+
},
|
|
149
|
+
relationships: {
|
|
150
|
+
groups: {
|
|
151
|
+
data: [
|
|
152
|
+
{
|
|
153
|
+
_id: 'group-id',
|
|
154
|
+
_type: 'io.cozy.contacts.groups'
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
const contactGroups = [
|
|
163
|
+
{
|
|
164
|
+
id: 'group-id',
|
|
165
|
+
name: 'Friends',
|
|
166
|
+
_id: 'group-id',
|
|
167
|
+
_type: 'io.cozy.contacts.groups'
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
setup({ contacts, contactGroups })
|
|
172
|
+
|
|
173
|
+
expect(screen.queryByText('My Self')).not.toBeInTheDocument()
|
|
174
|
+
expect(screen.getByText('Teagan Wolf')).toBeInTheDocument()
|
|
175
|
+
expect(screen.getByText('Friends - 1 members')).toBeInTheDocument()
|
|
176
|
+
})
|
|
177
|
+
|
|
119
178
|
it('should include unreachable members when flag sharing.show-recipient-groups is activated', async () => {
|
|
120
179
|
flag.mockReturnValue(true)
|
|
121
180
|
|