cozy-sharing 32.1.2 → 32.3.2

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/components/FederatedFolder/FederatedFolderModal.js +96 -22
  3. package/dist/components/FederatedFolder/FederatedFolderModal.spec.js +137 -219
  4. package/dist/components/Recipient/RecipientList.js +0 -15
  5. package/dist/components/ShareAutosuggest.js +1 -0
  6. package/dist/components/ShareAutosuggest.spec.js +12 -4
  7. package/dist/components/ShareDialogCozyToCozy.spec.js +55 -8
  8. package/dist/components/ShareDiscardChangesModal.js +35 -0
  9. package/dist/components/ShareModal/ShareModal.js +8 -8
  10. package/dist/components/ShareRecipientsInput.js +13 -3
  11. package/dist/components/ShareRecipientsInput.spec.js +63 -4
  12. package/dist/state.js +6 -1
  13. package/locales/en.json +10 -5
  14. package/locales/fr.json +10 -5
  15. package/locales/ru.json +10 -5
  16. package/locales/vi.json +10 -5
  17. package/package.json +2 -2
  18. package/src/components/FederatedFolder/FederatedFolderModal.jsx +230 -138
  19. package/src/components/FederatedFolder/FederatedFolderModal.spec.jsx +59 -134
  20. package/src/components/Recipient/RecipientList.jsx +0 -18
  21. package/src/components/ShareAutosuggest.jsx +1 -0
  22. package/src/components/ShareAutosuggest.spec.jsx +10 -5
  23. package/src/components/ShareDialogCozyToCozy.spec.jsx +50 -4
  24. package/src/components/ShareDiscardChangesModal.jsx +40 -0
  25. package/src/components/ShareModal/ShareModal.jsx +5 -5
  26. package/src/components/ShareRecipientsInput.jsx +11 -6
  27. package/src/components/ShareRecipientsInput.spec.jsx +59 -0
  28. package/src/state.js +5 -1
  29. package/dist/components/SharedDrive/SharedDriveRecipient.js +0 -61
  30. package/dist/components/SharedDrive/SharedDriveRecipientPermissions.js +0 -109
  31. package/dist/components/SharedDrive/SharedDriveRecipientStatus.js +0 -22
  32. package/dist/components/SharedFolder/DumbBatchSharedFolderModal.js +0 -145
  33. package/src/components/SharedDrive/SharedDriveRecipient.jsx +0 -62
  34. package/src/components/SharedDrive/SharedDriveRecipientPermissions.jsx +0 -78
  35. package/src/components/SharedDrive/SharedDriveRecipientStatus.jsx +0 -23
  36. package/src/components/SharedFolder/DumbBatchSharedFolderModal.jsx +0 -161
@@ -1,159 +1,251 @@
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'
6
+ import { FixedDialog } from 'cozy-ui/transpiled/react/CozyDialogs'
7
+ import Typography from 'cozy-ui/transpiled/react/Typography'
5
8
  import { useAlert } from 'cozy-ui/transpiled/react/providers/Alert'
9
+ import ConfirmDialogProvider, {
10
+ useConfirmDialog
11
+ } from 'cozy-ui/transpiled/react/providers/ConfirmDialog'
6
12
  import { useI18n } from 'twake-i18n'
7
13
 
8
14
  import { getOrCreateFromArray } from '../../helpers/contacts'
9
15
  import withLocales from '../../hoc/withLocales'
10
16
  import { usePendingRecipients } from '../../hooks/usePendingRecipients'
11
17
  import { useSharingContext } from '../../hooks/useSharingContext'
12
- import { DumbBatchSharedFolderModal } from '../SharedFolder/DumbBatchSharedFolderModal'
18
+ import AntivirusAlert from '../AntivirusAlert'
19
+ import { default as DumbShareByEmail } from '../ShareByEmail'
20
+ import ShareByLink from '../ShareByLink'
21
+ import WhoHasAccess from '../WhoHasAccess'
13
22
 
14
- export const FederatedFolderModal = withLocales(
15
- ({
23
+ const FederatedFolderModalContent = ({
24
+ onClose,
25
+ document: existingDocument,
26
+ autoOpenShareRestriction,
27
+ showGenerateLinkButton
28
+ }) => {
29
+ const client = useClient()
30
+ const { t } = useI18n()
31
+ const {
32
+ share,
33
+ getSharingLink,
34
+ getFederatedShareLink,
35
+ getDocumentPermissions,
36
+ getOwner,
37
+ getSharingById,
38
+ getRecipients,
39
+ revoke
40
+ } = useSharingContext()
41
+ const { showAlert } = useAlert()
42
+ const { showConfirmDialog, closeConfirmDialog } = useConfirmDialog()
43
+
44
+ const [sharingLink, setSharingLink] = useState(null)
45
+ const {
46
+ pendingRecipients,
47
+ setPendingRecipients,
48
+ selectedOption,
49
+ setSelectedOption
50
+ } = usePendingRecipients()
51
+
52
+ // share from CozyProvider is wired to callbacks and realtime that
53
+ // makes existingDocument and existingRecipients reactive to changes.
54
+ // But we want it to be reactive only when we revoke a member. When we add member
55
+ // we close the popup and we do not want to see briefly the new contacts added
56
+ // in members before the modal closes. That's why when clicking on "Share"
57
+ // we do not use the reactive existingDocument and existingRecipients.
58
+ const [isSending, setIsSending] = useState(false)
59
+ const [frozenDoc, setFrozenDoc] = useState(null)
60
+ const [frozenRecipients, setFrozenRecipients] = useState(null)
61
+
62
+ const handleCloseRequest = useCallback(() => {
63
+ if (pendingRecipients.length === 0) {
64
+ onClose()
65
+ return
66
+ }
67
+
68
+ showConfirmDialog({
69
+ title: t('ShareDiscardChangesModal.title'),
70
+ actions: (
71
+ <>
72
+ <Button
73
+ variant="secondary"
74
+ label={t('ShareDiscardChangesModal.cancel')}
75
+ className="u-fz-small"
76
+ onClick={closeConfirmDialog}
77
+ />
78
+ <Button
79
+ variant="primary"
80
+ label={t('ShareDiscardChangesModal.discard')}
81
+ className="u-fz-small"
82
+ onClick={() => {
83
+ closeConfirmDialog()
84
+ onClose()
85
+ }}
86
+ />
87
+ </>
88
+ )
89
+ })
90
+ }, [
91
+ closeConfirmDialog,
16
92
  onClose,
17
- document: existingDocument,
18
- autoOpenShareRestriction,
19
- showGenerateLinkButton
20
- }) => {
21
- const client = useClient()
22
- const { t } = useI18n()
23
- const {
24
- share,
25
- getSharingLink,
26
- getFederatedShareLink,
27
- getDocumentPermissions,
28
- getOwner,
29
- getSharingById,
30
- getRecipients,
31
- revoke
32
- } = useSharingContext()
33
- const { showAlert } = useAlert()
34
-
35
- const [sharingLink, setSharingLink] = useState(null)
36
- const {
37
- pendingRecipients,
38
- setPendingRecipients,
39
- selectedOption,
40
- setSelectedOption
41
- } = usePendingRecipients()
42
-
43
- // share from CozyProvider is wired to callbacks and realtime that
44
- // makes existingDocument and existingRecipients reactive to changes.
45
- // But we want it to be reactive only when we revoke a member. When we add member
46
- // we close the popup and we do not want to see briefly the new contacts added
47
- // in members before the modal closes. That's why when clicking on "Share"
48
- // we do not use the reactive existingDocument and existingRecipients.
49
- const [isSending, setIsSending] = useState(false)
50
- const [frozenDoc, setFrozenDoc] = useState(null)
51
- const [frozenRecipients, setFrozenRecipients] = useState(null)
52
-
53
- const documentPermissions = existingDocument
54
- ? getDocumentPermissions(existingDocument._id)
55
- : []
56
-
57
- useEffect(() => {
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
- }
93
+ pendingRecipients.length,
94
+ showConfirmDialog,
95
+ t
96
+ ])
68
97
 
69
- fetchSharingLink()
70
- }, [
71
- existingDocument,
72
- documentPermissions,
73
- getFederatedShareLink,
74
- getSharingLink,
75
- getOwner,
76
- getSharingById
77
- ])
78
-
79
- const folderName = existingDocument?.name || ''
80
-
81
- const onSend = async () => {
82
- if (pendingRecipients.length === 0) {
83
- onClose()
84
- return
85
- }
98
+ const documentPermissions = useMemo(
99
+ () =>
100
+ existingDocument ? getDocumentPermissions(existingDocument._id) : [],
101
+ [existingDocument, getDocumentPermissions]
102
+ )
103
+
104
+ useEffect(() => {
105
+ const fetchSharingLink = async () => {
106
+ if (!existingDocument) return
86
107
 
87
- setIsSending(true)
88
- setFrozenDoc(existingDocument)
89
- setFrozenRecipients(existingRecipients)
90
-
91
- try {
92
- const contacts = await getOrCreateFromArray(
93
- client,
94
- pendingRecipients,
95
- contact => client.create('io.cozy.contacts', contact)
96
- )
97
- const readWriteRecipients =
98
- selectedOption === 'readOnly' ? [] : contacts
99
- const readOnlyRecipients = selectedOption === 'readOnly' ? contacts : []
100
-
101
- await share({
102
- description: folderName,
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)
108
+ if (existingDocument.driveId) {
109
+ const link = await getFederatedShareLink(existingDocument)
110
+ setSharingLink(link)
111
+ } else {
112
+ setSharingLink(getSharingLink(existingDocument._id))
125
113
  }
126
114
  }
127
115
 
128
- const existingRecipients = existingDocument
129
- ? getRecipients(existingDocument._id)
130
- : []
131
-
132
- const modalTitle = t('FederatedFolder.shareTitle', { name: folderName })
133
- const isSharedDrive = Boolean(existingDocument?.driveId)
134
-
135
- return (
136
- <DumbBatchSharedFolderModal
137
- title={modalTitle}
138
- document={isSending ? frozenDoc : existingDocument}
139
- recipients={isSending ? frozenRecipients : existingRecipients}
140
- currentRecipients={isSending ? frozenRecipients : existingRecipients}
141
- onRevoke={revoke}
142
- onSend={onSend}
143
- onClose={onClose}
144
- sharingLink={sharingLink}
145
- shareLabel={t('FederatedFolder.share')}
146
- showShareByEmail={!isSharedDrive}
147
- autoOpenShareRestriction={autoOpenShareRestriction}
148
- showGenerateLinkButton={showGenerateLinkButton}
149
- pendingRecipients={pendingRecipients}
150
- onPendingRecipientsChange={setPendingRecipients}
151
- selectedOption={selectedOption}
152
- onSelectedOptionChange={setSelectedOption}
153
- />
154
- )
116
+ fetchSharingLink()
117
+ }, [
118
+ existingDocument,
119
+ documentPermissions,
120
+ getFederatedShareLink,
121
+ getSharingLink,
122
+ getOwner,
123
+ getSharingById
124
+ ])
125
+
126
+ const folderName = existingDocument?.name || ''
127
+
128
+ const onSend = async () => {
129
+ if (isSending || pendingRecipients.length === 0) {
130
+ onClose()
131
+ return
132
+ }
133
+
134
+ setIsSending(true)
135
+ setFrozenDoc(existingDocument)
136
+ setFrozenRecipients(existingRecipients)
137
+
138
+ try {
139
+ const contacts = await getOrCreateFromArray(
140
+ client,
141
+ pendingRecipients,
142
+ contact => client.create('io.cozy.contacts', contact)
143
+ )
144
+ const readWriteRecipients = selectedOption === 'readOnly' ? [] : contacts
145
+ const readOnlyRecipients = selectedOption === 'readOnly' ? contacts : []
146
+
147
+ await share({
148
+ description: folderName,
149
+ document: existingDocument,
150
+ recipients: readWriteRecipients,
151
+ readOnlyRecipients,
152
+ sharedDrive: true,
153
+ openSharing: false
154
+ })
155
+
156
+ showAlert({
157
+ message: t('FederatedFolder.successNotification'),
158
+ severity: 'success',
159
+ variant: 'filled'
160
+ })
161
+
162
+ onClose()
163
+ } catch (_err) {
164
+ showAlert({
165
+ message: t('FederatedFolder.errorNotification'),
166
+ severity: 'error',
167
+ variant: 'filled'
168
+ })
169
+ } finally {
170
+ setIsSending(false)
171
+ }
155
172
  }
156
- )
173
+
174
+ const existingRecipients = existingDocument
175
+ ? getRecipients(existingDocument._id)
176
+ : []
177
+
178
+ const modalTitle = t('FederatedFolder.shareTitle', { name: folderName })
179
+
180
+ return (
181
+ <FixedDialog
182
+ open
183
+ disableGutters
184
+ onClose={handleCloseRequest}
185
+ title={modalTitle}
186
+ classes={{ paper: 'u-ov-visible' }}
187
+ componentsProps={{
188
+ dialogContent: { className: 'u-ov-visible' }
189
+ }}
190
+ content={
191
+ <div>
192
+ <div className="u-ph-2">
193
+ <AntivirusAlert
194
+ document={isSending ? frozenDoc : existingDocument}
195
+ />
196
+ <Typography variant="h6" className="u-mt-1-half u-mb-half">
197
+ {t('Share.contacts.addUsers')}
198
+ </Typography>
199
+ <DumbShareByEmail
200
+ currentRecipients={
201
+ isSending ? frozenRecipients : existingRecipients
202
+ }
203
+ document={isSending ? frozenDoc : existingDocument}
204
+ documentType="Files"
205
+ pendingRecipients={pendingRecipients}
206
+ onPendingRecipientsChange={setPendingRecipients}
207
+ selectedOption={selectedOption}
208
+ onSelectedOptionChange={setSelectedOption}
209
+ />
210
+ </div>
211
+ <WhoHasAccess
212
+ isOwner
213
+ isSharedDrive
214
+ recipients={isSending ? frozenRecipients : existingRecipients}
215
+ document={isSending ? frozenDoc : existingDocument}
216
+ documentType="Files"
217
+ className="u-w-100"
218
+ onRevoke={revoke}
219
+ link={sharingLink}
220
+ />
221
+ </div>
222
+ }
223
+ actions={
224
+ <>
225
+ <ShareByLink
226
+ link={sharingLink}
227
+ document={isSending ? frozenDoc : existingDocument}
228
+ documentType="Files"
229
+ showGenerateLinkButton={showGenerateLinkButton}
230
+ autoOpenShareRestriction={autoOpenShareRestriction}
231
+ />
232
+ <Button
233
+ variant="primary"
234
+ label={t('FederatedFolder.share')}
235
+ disabled={isSending}
236
+ onClick={onSend}
237
+ />
238
+ </>
239
+ }
240
+ />
241
+ )
242
+ }
243
+
244
+ export const FederatedFolderModal = withLocales(props => (
245
+ <ConfirmDialogProvider>
246
+ <FederatedFolderModalContent {...props} />
247
+ </ConfirmDialogProvider>
248
+ ))
157
249
 
158
250
  FederatedFolderModal.propTypes = {
159
251
  onClose: PropTypes.func.isRequired,
@@ -41,41 +41,6 @@ jest.mock('../../hooks/usePendingRecipients', () => ({
41
41
  usePendingRecipients: jest.fn()
42
42
  }))
43
43
 
44
- jest.mock('../SharedFolder/DumbBatchSharedFolderModal', () => ({
45
- DumbBatchSharedFolderModal: ({
46
- title,
47
- recipients,
48
- onRevoke,
49
- onSend,
50
- onClose,
51
- sharingLink,
52
- pendingRecipients,
53
- selectedOption
54
- }) => (
55
- <div data-testid="dumb-modal">
56
- <span data-testid="title">{title}</span>
57
- <span data-testid="sharing-link">{sharingLink}</span>
58
- <span data-testid="recipients-count">{recipients.length}</span>
59
- <span data-testid="pending-recipients-count">
60
- {pendingRecipients.length}
61
- </span>
62
- <span data-testid="selected-option">{selectedOption}</span>
63
- <button data-testid="btn-send" onClick={onSend}>
64
- Send
65
- </button>
66
- <button
67
- data-testid="btn-revoke"
68
- onClick={() => onRevoke({ _id: 'folder-123' }, 'abc', 1)}
69
- >
70
- Revoke
71
- </button>
72
- <button data-testid="btn-close" onClick={onClose}>
73
- Close
74
- </button>
75
- </div>
76
- )
77
- }))
78
-
79
44
  const mockDocument = {
80
45
  _id: 'folder-123',
81
46
  name: 'My Test Folder',
@@ -145,46 +110,22 @@ describe('FederatedFolderModal', () => {
145
110
 
146
111
  describe('initialization', () => {
147
112
  it('should pass document name as title', async () => {
148
- const { getByTestId } = setup()
113
+ const { findByText } = setup()
149
114
 
150
- await waitFor(() => {
151
- expect(getByTestId('title').textContent).toBe('Share "My Test Folder"')
152
- })
115
+ await findByText('Share "My Test Folder"')
153
116
  })
154
117
 
155
118
  it('should pass default title when document has no name', async () => {
156
119
  const documentWithoutName = { _id: 'folder-456', path: '/test' }
157
- const { getByTestId } = setup({ document: documentWithoutName })
158
-
159
- await waitFor(() => {
160
- expect(getByTestId('title').textContent).toBe('Share ""')
161
- })
162
- })
120
+ const { findByText } = setup({ document: documentWithoutName })
163
121
 
164
- it('should pass sharing link to DumbBatchSharedFolderModal', async () => {
165
- const { getByTestId } = setup()
166
-
167
- await waitFor(() => {
168
- expect(getByTestId('sharing-link').textContent).toBe(
169
- 'https://example.com/share/abc123'
170
- )
171
- })
122
+ await findByText('Share ""')
172
123
  })
173
124
 
174
- it('should pass null sharing link when document is undefined', async () => {
175
- const { getByTestId } = setup({ document: undefined })
125
+ it('should show Copy link', async () => {
126
+ const { findByText } = setup()
176
127
 
177
- await waitFor(() => {
178
- expect(getByTestId('sharing-link').textContent).toBe('')
179
- })
180
- })
181
-
182
- it('should pass selectedOption from usePendingRecipients', async () => {
183
- const { getByTestId } = setup()
184
-
185
- await waitFor(() => {
186
- expect(getByTestId('selected-option').textContent).toBe('readWrite')
187
- })
128
+ await findByText('Copy link')
188
129
  })
189
130
  })
190
131
 
@@ -193,13 +134,11 @@ describe('FederatedFolderModal', () => {
193
134
 
194
135
  it('should call onClose and skip share when there are no pending recipients', async () => {
195
136
  // pendingRecipients defaults to [] via beforeEach
196
- const { getByTestId } = setup()
137
+ const { findByText } = setup()
197
138
 
198
- await waitFor(() => {
199
- expect(getByTestId('btn-send')).toBeTruthy()
200
- })
139
+ const sendButton = await findByText('Done')
201
140
 
202
- fireEvent.click(getByTestId('btn-send'))
141
+ fireEvent.click(sendButton)
203
142
 
204
143
  await waitFor(() => {
205
144
  expect(mockOnClose).toHaveBeenCalled()
@@ -216,13 +155,11 @@ describe('FederatedFolderModal', () => {
216
155
  })
217
156
  getOrCreateFromArray.mockResolvedValue([pendingRecipient])
218
157
  mockShare.mockResolvedValueOnce({})
219
- const { getByTestId } = setup()
158
+ const { findByText } = setup()
220
159
 
221
- await waitFor(() => {
222
- expect(getByTestId('btn-send')).toBeTruthy()
223
- })
160
+ const sendButton = await findByText('Done')
224
161
 
225
- fireEvent.click(getByTestId('btn-send'))
162
+ fireEvent.click(sendButton)
226
163
 
227
164
  await waitFor(() => {
228
165
  expect(mockShare).toHaveBeenCalledWith({
@@ -245,13 +182,11 @@ describe('FederatedFolderModal', () => {
245
182
  })
246
183
  getOrCreateFromArray.mockResolvedValue([pendingRecipient])
247
184
  mockShare.mockResolvedValueOnce({})
248
- const { getByTestId } = setup()
185
+ const { findByText } = setup()
249
186
 
250
- await waitFor(() => {
251
- expect(getByTestId('btn-send')).toBeTruthy()
252
- })
187
+ const sendButton = await findByText('Done')
253
188
 
254
- fireEvent.click(getByTestId('btn-send'))
189
+ fireEvent.click(sendButton)
255
190
 
256
191
  await waitFor(() => {
257
192
  expect(mockShowAlert).toHaveBeenCalledWith({
@@ -272,13 +207,11 @@ describe('FederatedFolderModal', () => {
272
207
  })
273
208
  getOrCreateFromArray.mockResolvedValue([pendingRecipient])
274
209
  mockShare.mockRejectedValueOnce(new Error('Share failed'))
275
- const { getByTestId } = setup()
210
+ const { findByText } = setup()
276
211
 
277
- await waitFor(() => {
278
- expect(getByTestId('btn-send')).toBeTruthy()
279
- })
212
+ const sendButton = await findByText('Done')
280
213
 
281
- fireEvent.click(getByTestId('btn-send'))
214
+ fireEvent.click(sendButton)
282
215
 
283
216
  await waitFor(() => {
284
217
  expect(mockShowAlert).toHaveBeenCalledWith({
@@ -291,66 +224,58 @@ describe('FederatedFolderModal', () => {
291
224
  })
292
225
  })
293
226
 
294
- describe('onRevoke callback', () => {
295
- it('should call revoke from sharing context for existing members', async () => {
296
- mockRevoke.mockResolvedValueOnce({})
297
- const { getByTestId } = setup()
227
+ describe('onClose callback', () => {
228
+ it('should call onClose when close button is clicked', async () => {
229
+ const { findByRole } = setup()
298
230
 
299
- await waitFor(() => {
300
- expect(getByTestId('dumb-modal')).toBeTruthy()
301
- })
231
+ const closeButton = await findByRole('button', { name: /close/i })
302
232
 
303
- fireEvent.click(getByTestId('btn-revoke'))
233
+ fireEvent.click(closeButton)
304
234
 
305
- await waitFor(() => {
306
- expect(mockRevoke).toHaveBeenCalledWith({ _id: 'folder-123' }, 'abc', 1)
307
- })
235
+ expect(mockOnClose).toHaveBeenCalled()
308
236
  })
309
- })
310
-
311
- describe('onClose callback', () => {
312
- it('should call onClose when close button is clicked', async () => {
313
- const { getByTestId } = setup()
314
237
 
315
- await waitFor(() => {
316
- expect(getByTestId('btn-close')).toBeTruthy()
238
+ it('should ask confirmation when close button is clicked with pending recipients', async () => {
239
+ usePendingRecipients.mockReturnValue({
240
+ pendingRecipients: [{ name: 'Alice', email: 'alice@example.com' }],
241
+ setPendingRecipients: jest.fn(),
242
+ selectedOption: 'readWrite',
243
+ setSelectedOption: jest.fn()
317
244
  })
318
245
 
319
- fireEvent.click(getByTestId('btn-close'))
246
+ const { getByRole, findByRole, getByText } = setup()
320
247
 
321
- expect(mockOnClose).toHaveBeenCalled()
322
- })
323
- })
248
+ const closeButton = await findByRole('button', { name: /close/i })
324
249
 
325
- describe('existing recipients', () => {
326
- const existingRecipients = [
327
- {
328
- index: 'sharing-abc-member-0',
329
- name: 'Charlie',
330
- email: 'charlie@example.com',
331
- status: 'owner',
332
- type: 'two-way',
333
- sharingId: 'abc',
334
- memberIndex: 0
335
- },
336
- {
337
- index: 'sharing-abc-member-1',
338
- name: 'Diana',
339
- email: 'diana@example.com',
340
- status: 'ready',
341
- type: 'one-way',
342
- sharingId: 'abc',
343
- memberIndex: 1
344
- }
345
- ]
250
+ fireEvent.click(closeButton)
346
251
 
347
- it('should display existing recipients from getRecipients', async () => {
348
- mockGetRecipients.mockReturnValue(existingRecipients)
349
- const { getByTestId } = setup()
252
+ expect(getByText("Discard the changes you haven't saved?")).toBeTruthy()
253
+ expect(mockOnClose).not.toHaveBeenCalled()
350
254
 
351
- await waitFor(() => {
352
- expect(getByTestId('recipients-count').textContent).toBe('2')
255
+ fireEvent.click(getByRole('button', { name: 'Cancel' }))
256
+
257
+ expect(mockOnClose).not.toHaveBeenCalled()
258
+ })
259
+
260
+ it('should close after discard confirmation', async () => {
261
+ usePendingRecipients.mockReturnValue({
262
+ pendingRecipients: [{ name: 'Alice', email: 'alice@example.com' }],
263
+ setPendingRecipients: jest.fn(),
264
+ selectedOption: 'readWrite',
265
+ setSelectedOption: jest.fn()
353
266
  })
267
+
268
+ const { getByRole, findByRole, getByText } = setup()
269
+
270
+ const closeButton = await findByRole('button', { name: /close/i })
271
+
272
+ fireEvent.click(closeButton)
273
+
274
+ expect(getByText("Discard the changes you haven't saved?")).toBeTruthy()
275
+
276
+ fireEvent.click(getByRole('button', { name: 'Discard' }))
277
+
278
+ expect(mockOnClose).toHaveBeenCalledTimes(1)
354
279
  })
355
280
  })
356
281
  })