cozy-sharing 28.5.0 → 28.6.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 +8 -0
- package/dist/SharingProvider.js +182 -86
- package/dist/SharingProvider.spec.js +152 -0
- package/dist/components/Recipient/LinkRecipient.js +6 -3
- package/dist/components/Recipient/LinkRecipient.stories.js +0 -1
- 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/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 +51 -1
- package/src/SharingProvider.spec.jsx +94 -0
- package/src/components/Recipient/LinkRecipient.jsx +10 -4
- package/src/components/Recipient/LinkRecipient.stories.jsx +0 -1
- 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/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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cozy-sharing",
|
|
3
|
-
"version": "28.
|
|
3
|
+
"version": "28.6.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": "64311a9d08372224b5214cbf49f7d658d93f9af9"
|
|
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)
|
|
@@ -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', () => {
|
|
@@ -26,8 +26,7 @@ const LinkRecipient = props => {
|
|
|
26
26
|
const { isMobile } = useBreakpoints()
|
|
27
27
|
const { getDocumentPermissions } = useSharingContext()
|
|
28
28
|
|
|
29
|
-
const { recipientConfirmationData, verifyRecipient,
|
|
30
|
-
props
|
|
29
|
+
const { recipientConfirmationData, verifyRecipient, fadeIn, document } = props
|
|
31
30
|
|
|
32
31
|
const permissions = getDocumentPermissions(document?._id)
|
|
33
32
|
const hasPassword = checkIsPermissionHasPassword(permissions)
|
|
@@ -44,7 +43,7 @@ const LinkRecipient = props => {
|
|
|
44
43
|
? t('Share.recipients.expires', {
|
|
45
44
|
date: dateFormatted
|
|
46
45
|
})
|
|
47
|
-
:
|
|
46
|
+
: ''
|
|
48
47
|
|
|
49
48
|
const RightPart = recipientConfirmationData ? (
|
|
50
49
|
<RecipientConfirm
|
|
@@ -74,7 +73,14 @@ const LinkRecipient = props => {
|
|
|
74
73
|
}
|
|
75
74
|
|
|
76
75
|
LinkRecipient.propTypes = {
|
|
77
|
-
|
|
76
|
+
recipientConfirmationData: PropTypes.oneOfType([
|
|
77
|
+
PropTypes.bool,
|
|
78
|
+
PropTypes.object
|
|
79
|
+
]),
|
|
80
|
+
verifyRecipient: PropTypes.func,
|
|
81
|
+
fadeIn: PropTypes.bool,
|
|
82
|
+
document: PropTypes.object,
|
|
83
|
+
permissions: PropTypes.arrayOf(PropTypes.object)
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
export default LinkRecipient
|
|
@@ -6,6 +6,17 @@ import { createMockClient } from 'cozy-client'
|
|
|
6
6
|
import MemberRecipient from './MemberRecipient'
|
|
7
7
|
import AppLike from '../../../test/AppLike'
|
|
8
8
|
|
|
9
|
+
const mockUpdateDocumentPermissions = jest.fn()
|
|
10
|
+
const mockUpdateSharingMemberType = jest.fn()
|
|
11
|
+
|
|
12
|
+
jest.mock('../../hooks/useSharingContext', () => ({
|
|
13
|
+
// eslint-disable-next-line react/display-name
|
|
14
|
+
useSharingContext: () => ({
|
|
15
|
+
updateDocumentPermissions: mockUpdateDocumentPermissions,
|
|
16
|
+
updateSharingMemberType: mockUpdateSharingMemberType
|
|
17
|
+
})
|
|
18
|
+
}))
|
|
19
|
+
|
|
9
20
|
describe('MemberRecipient component', () => {
|
|
10
21
|
const client = createMockClient({})
|
|
11
22
|
client.options = {
|
|
@@ -2,6 +2,7 @@ import React, { useState, useRef, useCallback } from 'react'
|
|
|
2
2
|
import { useI18n } from 'twake-i18n'
|
|
3
3
|
|
|
4
4
|
import { useClient } from 'cozy-client'
|
|
5
|
+
import minilog from 'cozy-minilog'
|
|
5
6
|
import ActionsMenu from 'cozy-ui/transpiled/react/ActionsMenu'
|
|
6
7
|
import {
|
|
7
8
|
makeActions,
|
|
@@ -10,9 +11,14 @@ import {
|
|
|
10
11
|
import DropdownButton from 'cozy-ui/transpiled/react/DropdownButton'
|
|
11
12
|
import Spinner from 'cozy-ui/transpiled/react/Spinner'
|
|
12
13
|
import Typography from 'cozy-ui/transpiled/react/Typography'
|
|
14
|
+
import { useAlert } from 'cozy-ui/transpiled/react/providers/Alert'
|
|
13
15
|
|
|
14
|
-
import { permission } from './actions/permission'
|
|
15
16
|
import { revokeMember } from './actions/revokeMember'
|
|
17
|
+
import { setReadOnlySharedPermission } from './actions/setReadOnlySharedPermission'
|
|
18
|
+
import { setReadWriteSharedPermission } from './actions/setReadWriteSharedPermission'
|
|
19
|
+
import { useSharingContext } from '../../hooks/useSharingContext'
|
|
20
|
+
|
|
21
|
+
const log = minilog('MemberRecipientPermissions')
|
|
16
22
|
|
|
17
23
|
const MemberRecipientPermissions = ({
|
|
18
24
|
isOwner,
|
|
@@ -30,6 +36,8 @@ const MemberRecipientPermissions = ({
|
|
|
30
36
|
const { t } = useI18n()
|
|
31
37
|
const buttonRef = useRef()
|
|
32
38
|
const client = useClient()
|
|
39
|
+
const { updateSharingMemberType } = useSharingContext()
|
|
40
|
+
const { showAlert } = useAlert()
|
|
33
41
|
|
|
34
42
|
const [revoking, setRevoking] = useState(false)
|
|
35
43
|
const [isMenuDisplayed, setMenuDisplayed] = useState(false)
|
|
@@ -42,27 +50,72 @@ const MemberRecipientPermissions = ({
|
|
|
42
50
|
!contactIsOwner &&
|
|
43
51
|
((instanceMatchesClient && !isOwner) || isOwner)
|
|
44
52
|
|
|
45
|
-
const toggleMenu = () =>
|
|
46
|
-
|
|
53
|
+
const toggleMenu = useCallback(() => {
|
|
54
|
+
setMenuDisplayed(displayed => !displayed)
|
|
55
|
+
}, [])
|
|
56
|
+
const hideMenu = useCallback(() => {
|
|
57
|
+
setMenuDisplayed(false)
|
|
58
|
+
}, [])
|
|
47
59
|
|
|
48
60
|
const handleRevocation = useCallback(async () => {
|
|
49
61
|
setRevoking(true)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
62
|
+
try {
|
|
63
|
+
if (isOwner) {
|
|
64
|
+
await onRevoke(document, sharingId, memberIndex)
|
|
65
|
+
} else {
|
|
66
|
+
await onRevokeSelf(document)
|
|
67
|
+
}
|
|
68
|
+
} finally {
|
|
69
|
+
setRevoking(false)
|
|
54
70
|
}
|
|
55
|
-
setRevoking(false)
|
|
56
71
|
}, [isOwner, onRevoke, onRevokeSelf, document, sharingId, memberIndex])
|
|
57
72
|
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
const setType = useCallback(
|
|
74
|
+
async newType => {
|
|
75
|
+
if (newType === type) {
|
|
76
|
+
hideMenu()
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
await updateSharingMemberType(sharingId, memberIndex, newType)
|
|
81
|
+
} catch (error) {
|
|
82
|
+
log.error('Failed to change member permission type', error)
|
|
83
|
+
showAlert({
|
|
84
|
+
message: t('Share.members.error.changePermission'),
|
|
85
|
+
severity: 'error',
|
|
86
|
+
variant: 'filled'
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
hideMenu()
|
|
90
|
+
},
|
|
91
|
+
[
|
|
92
|
+
hideMenu,
|
|
93
|
+
memberIndex,
|
|
94
|
+
sharingId,
|
|
95
|
+
showAlert,
|
|
96
|
+
t,
|
|
97
|
+
type,
|
|
98
|
+
updateSharingMemberType
|
|
99
|
+
]
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
const actions = makeActions(
|
|
103
|
+
[
|
|
104
|
+
setReadOnlySharedPermission,
|
|
105
|
+
setReadWriteSharedPermission,
|
|
106
|
+
divider,
|
|
107
|
+
revokeMember
|
|
108
|
+
],
|
|
109
|
+
{
|
|
110
|
+
client,
|
|
111
|
+
t,
|
|
112
|
+
type: type ?? 'one-way',
|
|
113
|
+
isOwner,
|
|
114
|
+
isSharedDrive,
|
|
115
|
+
setType,
|
|
116
|
+
handleRevocation
|
|
117
|
+
}
|
|
118
|
+
)
|
|
66
119
|
|
|
67
120
|
return (
|
|
68
121
|
<div className={className}>
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { render, fireEvent, screen, act, waitFor } from '@testing-library/react'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
import { createMockClient } from 'cozy-client'
|
|
5
|
+
|
|
6
|
+
import MemberRecipientPermissions from './MemberRecipientPermissions'
|
|
7
|
+
import AppLike from '../../../test/AppLike'
|
|
8
|
+
|
|
9
|
+
const mockUpdateSharingMemberType = jest.fn()
|
|
10
|
+
const mockShowAlert = jest.fn()
|
|
11
|
+
|
|
12
|
+
jest.mock('../../hooks/useSharingContext', () => ({
|
|
13
|
+
useSharingContext: () => ({
|
|
14
|
+
updateSharingMemberType: mockUpdateSharingMemberType
|
|
15
|
+
})
|
|
16
|
+
}))
|
|
17
|
+
|
|
18
|
+
jest.mock('cozy-ui/transpiled/react/providers/Alert', () => ({
|
|
19
|
+
__esModule: true,
|
|
20
|
+
default: ({ children }) => children,
|
|
21
|
+
useAlert: () => ({
|
|
22
|
+
showAlert: mockShowAlert
|
|
23
|
+
})
|
|
24
|
+
}))
|
|
25
|
+
|
|
26
|
+
describe('MemberRecipientPermissions component', () => {
|
|
27
|
+
const client = createMockClient({})
|
|
28
|
+
client.options = {
|
|
29
|
+
uri: 'http://cozy.local:8080'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const defaultProps = {
|
|
33
|
+
isOwner: true,
|
|
34
|
+
isSharedDrive: false,
|
|
35
|
+
status: 'ready',
|
|
36
|
+
instance: 'http://cozy.local:8080',
|
|
37
|
+
type: 'two-way',
|
|
38
|
+
document: { id: 'doc-123', type: 'io.cozy.files', path: '/test' },
|
|
39
|
+
sharingId: 'sharing-123',
|
|
40
|
+
memberIndex: 1,
|
|
41
|
+
onRevoke: jest.fn(),
|
|
42
|
+
onRevokeSelf: jest.fn()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
jest.clearAllMocks()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const setup = props => {
|
|
50
|
+
return render(
|
|
51
|
+
<AppLike client={client}>
|
|
52
|
+
<MemberRecipientPermissions {...defaultProps} {...props} />
|
|
53
|
+
</AppLike>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let createRangeBackup
|
|
58
|
+
|
|
59
|
+
beforeAll(() => {
|
|
60
|
+
createRangeBackup = global.document.createRange
|
|
61
|
+
global.document.createRange = jest.fn(() => ({
|
|
62
|
+
setStart: () => {},
|
|
63
|
+
setEnd: () => {},
|
|
64
|
+
commonAncestorContainer: {
|
|
65
|
+
nodeName: 'BODY',
|
|
66
|
+
ownerDocument: document
|
|
67
|
+
}
|
|
68
|
+
}))
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
afterAll(() => {
|
|
72
|
+
global.document.createRange = createRangeBackup
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('should render the current permission type', () => {
|
|
76
|
+
setup()
|
|
77
|
+
expect(screen.getByText('can change')).toBeInTheDocument()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('should call updateSharingMemberType when changing to one-way', async () => {
|
|
81
|
+
mockUpdateSharingMemberType.mockResolvedValue(undefined)
|
|
82
|
+
|
|
83
|
+
const { getByText } = setup({ type: 'two-way' })
|
|
84
|
+
|
|
85
|
+
fireEvent.click(getByText('can change'))
|
|
86
|
+
fireEvent.click(getByText('Can view'))
|
|
87
|
+
|
|
88
|
+
expect(mockUpdateSharingMemberType).toHaveBeenCalledWith(
|
|
89
|
+
'sharing-123',
|
|
90
|
+
1,
|
|
91
|
+
'one-way'
|
|
92
|
+
)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('should call updateSharingMemberType when changing to two-way', async () => {
|
|
96
|
+
mockUpdateSharingMemberType.mockResolvedValue(undefined)
|
|
97
|
+
|
|
98
|
+
const { getByText } = setup({ type: 'one-way' })
|
|
99
|
+
|
|
100
|
+
fireEvent.click(getByText('can view'))
|
|
101
|
+
fireEvent.click(getByText('Can change'))
|
|
102
|
+
|
|
103
|
+
expect(mockUpdateSharingMemberType).toHaveBeenCalledWith(
|
|
104
|
+
'sharing-123',
|
|
105
|
+
1,
|
|
106
|
+
'two-way'
|
|
107
|
+
)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('should show error alert when updateSharingMemberType fails', async () => {
|
|
111
|
+
mockUpdateSharingMemberType.mockRejectedValue(new Error('Network error'))
|
|
112
|
+
|
|
113
|
+
const { getByText } = setup({ type: 'two-way' })
|
|
114
|
+
|
|
115
|
+
await act(async () => {
|
|
116
|
+
fireEvent.click(getByText('can change'))
|
|
117
|
+
fireEvent.click(getByText('Can view'))
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
await waitFor(() => {
|
|
121
|
+
expect(mockShowAlert).toHaveBeenCalledWith({
|
|
122
|
+
message:
|
|
123
|
+
'An error occurred while changing the permissions. Please try again.',
|
|
124
|
+
severity: 'error',
|
|
125
|
+
variant: 'filled'
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('should not call updateSharingMemberType when selecting same type', async () => {
|
|
131
|
+
mockUpdateSharingMemberType.mockResolvedValue(undefined)
|
|
132
|
+
|
|
133
|
+
const { getByText } = setup({ type: 'two-way' })
|
|
134
|
+
|
|
135
|
+
fireEvent.click(getByText('can change'))
|
|
136
|
+
fireEvent.click(getByText('Can change'))
|
|
137
|
+
|
|
138
|
+
expect(mockUpdateSharingMemberType).not.toHaveBeenCalled()
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('should not render menu when user is not owner and not the shared drive contact', () => {
|
|
142
|
+
const { queryByText } = setup({
|
|
143
|
+
isOwner: false,
|
|
144
|
+
instance: 'http://other-cozy.local:8080'
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
expect(queryByText('can change')).not.toBeInTheDocument()
|
|
148
|
+
expect(queryByText('can view')).not.toBeInTheDocument()
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('should render menu for owner', () => {
|
|
152
|
+
const { getByText } = setup({ isOwner: true })
|
|
153
|
+
|
|
154
|
+
expect(getByText('can change')).toBeInTheDocument()
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('should render menu for the user themselves', () => {
|
|
158
|
+
const { getByText } = setup({
|
|
159
|
+
isOwner: false,
|
|
160
|
+
instance: 'http://cozy.local:8080'
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
expect(getByText('can change')).toBeInTheDocument()
|
|
164
|
+
})
|
|
165
|
+
})
|
|
@@ -8,7 +8,7 @@ import Radios from 'cozy-ui/transpiled/react/Radios'
|
|
|
8
8
|
const makeComponent = ({ label, type, onClick }) => {
|
|
9
9
|
const Component = forwardRef((props, ref) => {
|
|
10
10
|
return (
|
|
11
|
-
<ActionsMenuItem {...props} ref={ref}
|
|
11
|
+
<ActionsMenuItem {...props} ref={ref} onClick={onClick}>
|
|
12
12
|
<ListItemIcon>
|
|
13
13
|
<Radios checked={type === 'one-way'} />
|
|
14
14
|
</ListItemIcon>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
import { setReadOnlySharedPermission } from './setReadOnlySharedPermission'
|
|
5
|
+
import AppLike from '../../../../test/AppLike'
|
|
6
|
+
|
|
7
|
+
const mockT = key => {
|
|
8
|
+
const translations = {
|
|
9
|
+
'Share.type.one-way': 'Can view',
|
|
10
|
+
'Share.type.two-way': 'Can change'
|
|
11
|
+
}
|
|
12
|
+
return translations[key] || key
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('setReadOnlySharedPermission', () => {
|
|
16
|
+
let setType
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
setType = jest.fn()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('should return correct action structure', () => {
|
|
23
|
+
const action = setReadOnlySharedPermission({
|
|
24
|
+
t: mockT,
|
|
25
|
+
type: 'one-way',
|
|
26
|
+
setType
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
expect(action.name).toBe('setReadOnlySharedPermission')
|
|
30
|
+
expect(action.label).toBe('Can view')
|
|
31
|
+
expect(action.icon).toBeNull()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('should call setType with one-way when action is triggered', () => {
|
|
35
|
+
const action = setReadOnlySharedPermission({
|
|
36
|
+
t: mockT,
|
|
37
|
+
type: 'two-way',
|
|
38
|
+
setType
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
action.action()
|
|
42
|
+
|
|
43
|
+
expect(setType).toHaveBeenCalledWith('one-way')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('should render radio as checked when type is one-way', () => {
|
|
47
|
+
const { Component } = setReadOnlySharedPermission({
|
|
48
|
+
t: mockT,
|
|
49
|
+
type: 'one-way',
|
|
50
|
+
setType
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
render(
|
|
54
|
+
<AppLike>
|
|
55
|
+
<Component />
|
|
56
|
+
</AppLike>
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
expect(screen.getByRole('radio')).toBeChecked()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('should render radio as unchecked when type is two-way', () => {
|
|
63
|
+
const { Component } = setReadOnlySharedPermission({
|
|
64
|
+
t: mockT,
|
|
65
|
+
type: 'two-way',
|
|
66
|
+
setType
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
render(
|
|
70
|
+
<AppLike>
|
|
71
|
+
<Component />
|
|
72
|
+
</AppLike>
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
expect(screen.getByRole('radio')).not.toBeChecked()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('should call setType with one-way when the component is clicked', () => {
|
|
79
|
+
const { Component } = setReadOnlySharedPermission({
|
|
80
|
+
t: mockT,
|
|
81
|
+
type: 'two-way',
|
|
82
|
+
setType
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
render(
|
|
86
|
+
<AppLike>
|
|
87
|
+
<Component />
|
|
88
|
+
</AppLike>
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
fireEvent.click(screen.getByText('Can view'))
|
|
92
|
+
|
|
93
|
+
expect(setType).toHaveBeenCalledWith('one-way')
|
|
94
|
+
})
|
|
95
|
+
})
|
|
@@ -8,7 +8,7 @@ import Radios from 'cozy-ui/transpiled/react/Radios'
|
|
|
8
8
|
const makeComponent = ({ label, type, onClick }) => {
|
|
9
9
|
const Component = forwardRef((props, ref) => {
|
|
10
10
|
return (
|
|
11
|
-
<ActionsMenuItem {...props} ref={ref}
|
|
11
|
+
<ActionsMenuItem {...props} ref={ref} onClick={onClick}>
|
|
12
12
|
<ListItemIcon>
|
|
13
13
|
<Radios checked={type === 'two-way'} />
|
|
14
14
|
</ListItemIcon>
|