cozy-sharing 37.0.2 → 37.2.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/OpenSharingLinkButton.js +1 -1
  3. package/dist/SharingProvider.js +315 -138
  4. package/dist/SharingProvider.spec.js +454 -240
  5. package/dist/actions/addToCozySharingLink.js +1 -1
  6. package/dist/actions/createCozySharingLink.js +1 -1
  7. package/dist/actions/shareNative.js +1 -1
  8. package/dist/actions/syncToCozySharingLink.js +1 -1
  9. package/dist/components/Avatar/AvatarList.js +1 -1
  10. package/dist/components/Recipient/GroupRecipient.js +0 -2
  11. package/dist/components/Recipient/GroupRecipientDetail.js +2 -25
  12. package/dist/components/Recipient/GroupRecipientDetailWithoutAccess.js +1 -1
  13. package/dist/components/Recipient/LinkRecipient.js +1 -1
  14. package/dist/components/Recipient/LinkRecipientLite.js +1 -1
  15. package/dist/components/Recipient/actions/revokeLink.js +1 -1
  16. package/dist/components/ShareAutosuggest.js +6 -6
  17. package/dist/components/ShareButton.js +1 -1
  18. package/dist/components/ShareByLink.js +1 -1
  19. package/dist/components/ShareLinkAccessModal/ShareLinkAccessModal.js +316 -0
  20. package/dist/components/ShareLinkAccessModal/ShareLinkAccessModal.spec.js +372 -0
  21. package/dist/components/ShareLinkAccessModal/index.js +1 -0
  22. package/dist/components/ShareRestrictionModal/BoxDate.js +1 -1
  23. package/dist/components/ShareRestrictionModal/BoxEditingRights.js +1 -1
  24. package/dist/components/ShareRestrictionModal/BoxPassword.js +1 -1
  25. package/dist/components/ShareRestrictionModal/ShareRestrictionModal.js +1 -1
  26. package/dist/components/ShareRestrictionModal/helpers.js +36 -18
  27. package/dist/components/ShareRestrictionModal/helpers.spec.js +3 -3
  28. package/dist/components/SharedBadge.js +1 -1
  29. package/dist/components/SharedDrive/SharedDriveHeader.js +1 -1
  30. package/dist/index.js +1 -0
  31. package/locales/en.json +21 -3
  32. package/locales/fr.json +22 -4
  33. package/locales/ru.json +21 -3
  34. package/locales/vi.json +21 -3
  35. package/package.json +3 -3
  36. package/src/OpenSharingLinkButton.jsx +1 -1
  37. package/src/SharingProvider.jsx +91 -27
  38. package/src/SharingProvider.spec.jsx +212 -60
  39. package/src/actions/addToCozySharingLink.js +1 -1
  40. package/src/actions/createCozySharingLink.js +1 -1
  41. package/src/actions/shareNative.js +1 -1
  42. package/src/actions/syncToCozySharingLink.js +1 -1
  43. package/src/components/Avatar/AvatarList.jsx +1 -1
  44. package/src/components/Recipient/GroupRecipient.jsx +1 -2
  45. package/src/components/Recipient/GroupRecipientDetail.jsx +3 -33
  46. package/src/components/Recipient/GroupRecipientDetailWithoutAccess.jsx +1 -1
  47. package/src/components/Recipient/LinkRecipient.jsx +1 -1
  48. package/src/components/Recipient/LinkRecipientLite.jsx +1 -1
  49. package/src/components/Recipient/actions/revokeLink.js +1 -1
  50. package/src/components/ShareAutosuggest.jsx +3 -4
  51. package/src/components/ShareButton.jsx +1 -1
  52. package/src/components/ShareByLink.jsx +1 -1
  53. package/src/components/ShareLinkAccessModal/ShareLinkAccessModal.jsx +293 -0
  54. package/src/components/ShareLinkAccessModal/ShareLinkAccessModal.spec.jsx +291 -0
  55. package/src/components/ShareLinkAccessModal/index.js +1 -0
  56. package/src/components/ShareRestrictionModal/BoxDate.jsx +1 -1
  57. package/src/components/ShareRestrictionModal/BoxEditingRights.jsx +1 -1
  58. package/src/components/ShareRestrictionModal/BoxPassword.jsx +1 -1
  59. package/src/components/ShareRestrictionModal/ShareRestrictionModal.jsx +1 -1
  60. package/src/components/ShareRestrictionModal/helpers.js +30 -6
  61. package/src/components/ShareRestrictionModal/helpers.spec.js +3 -3
  62. package/src/components/SharedBadge.jsx +2 -1
  63. package/src/components/SharedDrive/SharedDriveHeader.jsx +1 -1
  64. package/src/helpers/sharings.js +0 -1
  65. package/src/index.jsx +1 -0
package/locales/ru.json CHANGED
@@ -391,10 +391,8 @@
391
391
  "secondary_you": "включая вас"
392
392
  },
393
393
  "GroupRecipientDetail": {
394
- "subtitle": "Управляется %{ownerName}",
395
394
  "empty": {
396
- "content": "Ваша группа контактов пуста. Добавьте участников, чтобы начать делиться с ними файлами!",
397
- "action": "Открыть Контакты"
395
+ "content": "Эта группа пуста. Чтобы добавить участников в эту группу, обратитесь к администратору."
398
396
  },
399
397
  "withAccess": {
400
398
  "title": "Доступ к элементу"
@@ -462,5 +460,25 @@
462
460
  "successNotification": "Командный диск был изменён",
463
461
  "errorNotification": "Ошибка при модификации командного диска"
464
462
  }
463
+ },
464
+ "ShareLinkAccessModal": {
465
+ "title": "Настроить доступ по ссылке",
466
+ "introText": "Выберите, что получатели могут делать с этими файлами.",
467
+ "anyoneWithLink": "Разрешить доступ всем, у кого есть ссылка",
468
+ "settings": "Настройки доступа",
469
+ "accessLevel": "Уровень доступа",
470
+ "viewer": "Просмотр",
471
+ "editor": "Редактирование",
472
+ "settingsTitle": "Настройки доступа",
473
+ "expiry": "Установить срок действия",
474
+ "expiryDate": "Дата истечения",
475
+ "password": "Требовать пароль",
476
+ "passwordLabel": "Пароль",
477
+ "passwordTooShort": "Пароль должен содержать не менее %{count} символов.",
478
+ "cancel": "Отмена",
479
+ "addLinks": "Добавить ссылки",
480
+ "error": {
481
+ "persistence": "Не удалось сохранить разрешения ссылки. Попробуйте еще раз."
482
+ }
465
483
  }
466
484
  }
package/locales/vi.json CHANGED
@@ -391,10 +391,8 @@
391
391
  "secondary_you": "bao gồm cả bạn"
392
392
  },
393
393
  "GroupRecipientDetail": {
394
- "subtitle": "Được quản lý bởi %{ownerName}",
395
394
  "empty": {
396
- "content": "Nhóm liên hệ của bạn trống! Nhanh chóng thêm thành viên để bắt đầu chia sẻ các tài liệu quý giá của bạn với họ!",
397
- "action": "Mở Danh bạ"
395
+ "content": "Nhóm này hiện không thành viên nào. Để thêm thành viên vào nhóm này, vui lòng liên hệ với quản trị viên."
398
396
  },
399
397
  "withAccess": {
400
398
  "title": "Quyền truy cập vào mục"
@@ -462,5 +460,25 @@
462
460
  "successNotification": "Ổ đĩa nhóm đã được sửa đổi",
463
461
  "errorNotification": "Lỗi khi sửa đổi ổ đĩa nhóm"
464
462
  }
463
+ },
464
+ "ShareLinkAccessModal": {
465
+ "title": "Cài đặt quyền truy cập liên kết",
466
+ "introText": "Chọn những gì người nhận có thể làm với các tệp này.",
467
+ "anyoneWithLink": "Cho phép bất kỳ ai có liên kết truy cập",
468
+ "settings": "Cài đặt chia sẻ",
469
+ "accessLevel": "Cấp độ truy cập",
470
+ "viewer": "Người xem",
471
+ "editor": "Người chỉnh sửa",
472
+ "settingsTitle": "Cài đặt chia sẻ",
473
+ "expiry": "Đặt ngày hết hạn",
474
+ "expiryDate": "Ngày hết hạn",
475
+ "password": "Yêu cầu mật khẩu",
476
+ "passwordLabel": "Mật khẩu",
477
+ "passwordTooShort": "Mật khẩu phải có ít nhất %{count} ký tự.",
478
+ "cancel": "Hủy",
479
+ "addLinks": "Thêm liên kết",
480
+ "error": {
481
+ "persistence": "Không thể lưu quyền của liên kết. Vui lòng thử lại."
482
+ }
465
483
  }
466
484
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-sharing",
3
- "version": "37.0.2",
3
+ "version": "37.2.0",
4
4
  "description": "Provides sharing login for React applications.",
5
5
  "main": "dist/index.js",
6
6
  "author": "Cozy",
@@ -59,7 +59,7 @@
59
59
  "cozy-intent": "^2.31.1",
60
60
  "cozy-minilog": "^3.10.1",
61
61
  "cozy-ui": "^140.5.0",
62
- "cozy-ui-plus": "^12.1.0",
62
+ "cozy-ui-plus": "^12.1.2",
63
63
  "jest": "30.3.0",
64
64
  "jest-environment-jsdom": "30.3.0",
65
65
  "react": "16.12.0",
@@ -85,5 +85,5 @@
85
85
  "sideEffects": [
86
86
  "*.css"
87
87
  ],
88
- "gitHead": "22faa5b53511d7720c575564ec52f446743209e6"
88
+ "gitHead": "0cb65d680568b4cec09ebc03f324878cdcc7242c"
89
89
  }
@@ -1,7 +1,7 @@
1
- import { Icon } from '@linagora/twake-icons'
2
1
  import PropTypes from 'prop-types'
3
2
  import React from 'react'
4
3
 
4
+ import { Icon } from '@linagora/twake-icons'
5
5
  import Button from 'cozy-ui/transpiled/react/Buttons'
6
6
  import { useI18n } from 'twake-i18n'
7
7
 
@@ -3,7 +3,10 @@ import React, { Component } from 'react'
3
3
  import { withClient } from 'cozy-client'
4
4
  import minilog from 'cozy-minilog'
5
5
 
6
- import { generateShareLinkFromFile } from './components/ShareRestrictionModal/helpers'
6
+ import {
7
+ generateShareLinkFromFile,
8
+ getLinkAccessOptions
9
+ } from './components/ShareRestrictionModal/helpers'
7
10
  import SharingContext from './context'
8
11
  import { fetchNextPermissions } from './fetchNextPermissions'
9
12
  import { fetchFilesPaths } from './helpers/files'
@@ -41,6 +44,7 @@ import reducer, {
41
44
  getSharedDocIdsBySharings,
42
45
  getDocumentSharing,
43
46
  getDocumentPermissions,
47
+ getPermissionDocIds,
44
48
  hasSharedParent,
45
49
  hasSharedChild,
46
50
  getSharedParentPath,
@@ -52,6 +56,8 @@ const log = minilog('SharingProvider')
52
56
  const SHARING_DOCTYPE = 'io.cozy.sharings'
53
57
  const PERMISSION_DOCTYPE = 'io.cozy.permissions'
54
58
 
59
+ const getDocumentId = document => document?._id || document?.id || null
60
+
55
61
  export class SharingProvider extends Component {
56
62
  constructor(props, context) {
57
63
  super(props, context)
@@ -90,6 +96,7 @@ export class SharingProvider extends Component {
90
96
  revokeGroup: this.revokeGroup,
91
97
  revokeSelf: this.revokeSelf,
92
98
  shareByLink: this.shareByLink,
99
+ ensureSharingLink: this.ensureSharingLink,
93
100
  getFederatedShareLink: this.getFederatedShareLink,
94
101
  fetchSharedDriveSharingLinks: this.fetchSharedDriveSharingLinks,
95
102
  updateDocumentPermissions: this.updateDocumentPermissions,
@@ -430,10 +437,76 @@ export class SharingProvider extends Component {
430
437
  }
431
438
  }
432
439
 
440
+ fetchSharingLinks = async (document, permissionCol = this.permissionCol) => {
441
+ const documentId = getDocumentId(document)
442
+ if (!documentId) return []
443
+
444
+ const response = document.driveId
445
+ ? await permissionCol.findLinksByIds([documentId])
446
+ : await permissionCol.findLinksByDoctype(this.props.doctype)
447
+ const permissions = (response?.data || []).filter(
448
+ permission =>
449
+ document.driveId || getPermissionDocIds(permission).includes(documentId)
450
+ )
451
+ const existingPermissions = getDocumentPermissions(this.state, documentId)
452
+ const existingPermissionIds = existingPermissions.map(
453
+ permission => permission.id
454
+ )
455
+ const newPermissions = permissions.filter(
456
+ permission => !existingPermissionIds.includes(permission.id)
457
+ )
458
+
459
+ if (newPermissions.length > 0) {
460
+ this.dispatch(addSharingLink(newPermissions))
461
+ }
462
+
463
+ return permissions
464
+ }
465
+
466
+ ensureSharingLink = async (document, linkAccess) => {
467
+ const documentId = getDocumentId(document)
468
+ if (!documentId) {
469
+ throw new Error('Sharing links require a document id')
470
+ }
471
+
472
+ const permissions = document.driveId
473
+ ? await this.fetchSharedDriveSharingLinks(document)
474
+ : await this.fetchSharingLinks(document)
475
+ const { verbs, expiresAt, ttl, password } = getLinkAccessOptions(linkAccess)
476
+ const permissionResponse =
477
+ permissions.length > 0
478
+ ? (
479
+ await this.updateDocumentPermissions(
480
+ { ...document, id: documentId },
481
+ { verbs, expiresAt, password },
482
+ permissions
483
+ )
484
+ )[0]
485
+ : await this.shareByLink(document, { verbs, ttl, password })
486
+ const permission = permissionResponse?.data
487
+ const sharecode = getShortcode(permission)
488
+
489
+ if (!sharecode) {
490
+ throw new Error('Sharing permission does not contain a shortcode')
491
+ }
492
+
493
+ return {
494
+ documentId,
495
+ url: generateShareLinkFromFile({
496
+ client: this.props.client,
497
+ file: document,
498
+ sharecode,
499
+ getOwner: this.state.getOwner,
500
+ getSharingById: this.state.getSharingById,
501
+ documentType: this.state.documentType
502
+ })
503
+ }
504
+ }
505
+
433
506
  getFederatedShareLink = document => {
434
507
  if (!document?.driveId) return null
435
508
 
436
- const documentId = document._id || document.id
509
+ const documentId = getDocumentId(document)
437
510
  if (!documentId) return null
438
511
 
439
512
  const permissions = getDocumentPermissions(this.state, documentId)
@@ -473,25 +546,7 @@ export class SharingProvider extends Component {
473
546
  const drivePermissionCollection = client.collection('io.cozy.permissions', {
474
547
  driveId: document.driveId
475
548
  })
476
- const documentId = document._id || document.id
477
- if (!documentId) return []
478
-
479
- const resp = await drivePermissionCollection.findLinksByIds([documentId])
480
- const permissions = resp.data || []
481
-
482
- const existingPermissions = getDocumentPermissions(this.state, documentId)
483
- const existingPermissionIds = existingPermissions.map(
484
- permission => permission.id
485
- )
486
- const newPermissions = permissions.filter(
487
- permission => !existingPermissionIds.includes(permission.id)
488
- )
489
-
490
- if (newPermissions.length > 0) {
491
- this.dispatch(addSharingLink(newPermissions))
492
- }
493
-
494
- return permissions
549
+ return this.fetchSharingLinks(document, drivePermissionCollection)
495
550
  }
496
551
 
497
552
  updateSharedDrivePermissions = async (
@@ -522,12 +577,20 @@ export class SharingProvider extends Component {
522
577
  * @param {string[]} options.verbs The new verbs to use for the permission, eg. ['GET']
523
578
  * @param {string} [options.expiresAt] The new expiration date for the permission
524
579
  * @param {string} [options.password] The new password for the permission
580
+ * @param {object[]|null} [permissionsToUpdate] Permission documents to update
581
+ * instead of the document's permissions from state
525
582
  *
526
583
  * @return {Array}
527
584
  */
528
- updateDocumentPermissions = async (document, options) => {
585
+ updateDocumentPermissions = async (
586
+ document,
587
+ options,
588
+ permissionsToUpdate = null
589
+ ) => {
529
590
  const { verbs, expiresAt, password } = options
530
- const permissions = getDocumentPermissions(this.state, document.id)
591
+ const documentId = getDocumentId(document)
592
+ const permissions =
593
+ permissionsToUpdate || getDocumentPermissions(this.state, documentId)
531
594
 
532
595
  if (!permissions || permissions.length === 0) {
533
596
  return []
@@ -535,10 +598,11 @@ export class SharingProvider extends Component {
535
598
 
536
599
  const responses = await Promise.all(
537
600
  permissions.map(async permissionDocument => {
538
- const updatedPermissions = permissionDocument.attributes.permissions
539
- Object.keys(updatedPermissions).forEach(permType => {
540
- updatedPermissions[permType].verbs = verbs
541
- })
601
+ const updatedPermissions = Object.fromEntries(
602
+ Object.entries(permissionDocument.attributes.permissions).map(
603
+ ([permissionType, rules]) => [permissionType, { ...rules, verbs }]
604
+ )
605
+ )
542
606
 
543
607
  const resp = document.driveId
544
608
  ? await this.updateSharedDrivePermissions(
@@ -1,4 +1,5 @@
1
1
  import { act, render, screen } from '@testing-library/react'
2
+ import { endOfDay } from 'date-fns'
2
3
  import React from 'react'
3
4
 
4
5
  import { createMockClient } from 'cozy-client'
@@ -23,6 +24,24 @@ const AppWrapper = ({ children, client, isPublic }) => {
23
24
  )
24
25
  }
25
26
 
27
+ const setupProvider = (client, options = {}, state = reducer()) => {
28
+ const provider = new SharingProvider({ client, ...options })
29
+ provider.state = {
30
+ ...provider.state,
31
+ ...state,
32
+ onShared: provider.state.onShared
33
+ }
34
+ provider.dispatch = jest.fn(action => {
35
+ const onShared = provider.state.onShared
36
+ provider.state = {
37
+ ...provider.state,
38
+ ...reducer(provider.state, action),
39
+ onShared
40
+ }
41
+ })
42
+ return provider
43
+ }
44
+
26
45
  describe('allLoaded', () => {
27
46
  const client = createMockClient({})
28
47
  client.isLogged = true
@@ -142,11 +161,7 @@ describe('shareByLink', () => {
142
161
  createSharingLink: mockCreateSharingLink
143
162
  })
144
163
 
145
- const provider = new SharingProvider({ client: mockClient })
146
- provider.state = reducer()
147
- provider.dispatch = jest.fn(action => {
148
- provider.state = reducer(provider.state, action)
149
- })
164
+ const provider = setupProvider(mockClient)
150
165
 
151
166
  await provider.shareByLink(driveFile, { verbs: ['GET'] })
152
167
 
@@ -181,12 +196,8 @@ describe('shareByLink', () => {
181
196
  createSharingLink: mockCreateSharingLink
182
197
  })
183
198
 
184
- const provider = new SharingProvider({ client: mockClient })
185
- provider.state = reducer()
199
+ const provider = setupProvider(mockClient)
186
200
  provider.permissionCol = { createSharingLink: mockCreateSharingLink }
187
- provider.dispatch = jest.fn(action => {
188
- provider.state = reducer(provider.state, action)
189
- })
190
201
 
191
202
  await provider.shareByLink(regularFile, { verbs: ['GET'] })
192
203
 
@@ -197,6 +208,163 @@ describe('shareByLink', () => {
197
208
  })
198
209
  })
199
210
 
211
+ describe('ensureSharingLink', () => {
212
+ const permission = {
213
+ id: 'permission-id',
214
+ attributes: {
215
+ permissions: {
216
+ rule0: {
217
+ verbs: ['GET'],
218
+ values: ['file-id']
219
+ }
220
+ },
221
+ shortcodes: { code: 'share-code' }
222
+ }
223
+ }
224
+ const document = { _id: 'file-id', name: 'invoice.pdf' }
225
+ const linkAccess = {
226
+ editingRights: 'write',
227
+ dateEnabled: false,
228
+ selectedDate: null,
229
+ passwordEnabled: false,
230
+ password: ''
231
+ }
232
+
233
+ it('fetches regular links with the regular permission API', async () => {
234
+ const client = createMockClient({})
235
+ client.getStackClient = () => ({ uri: 'https://cozy.example' })
236
+ client.collection = jest.fn().mockReturnValue({})
237
+ client.capabilities = { flat_subdomains: true }
238
+ const provider = setupProvider(client, {
239
+ doctype: 'io.cozy.files',
240
+ documentType: 'Files'
241
+ })
242
+ const unrelatedPermission = {
243
+ ...permission,
244
+ id: 'unrelated-permission-id',
245
+ attributes: {
246
+ ...permission.attributes,
247
+ permissions: {
248
+ rule0: {
249
+ ...permission.attributes.permissions.rule0,
250
+ values: ['other']
251
+ }
252
+ }
253
+ }
254
+ }
255
+ const findLinksByDoctype = jest
256
+ .fn()
257
+ .mockResolvedValue({ data: [permission, unrelatedPermission] })
258
+ const findLinksByIds = jest.fn(() => {
259
+ throw new Error('findLinksByIds is only supported for shared drives')
260
+ })
261
+ provider.permissionCol = { findLinksByDoctype, findLinksByIds }
262
+ provider.updateDocumentPermissions = jest
263
+ .fn()
264
+ .mockResolvedValue([{ data: permission }])
265
+
266
+ await provider.ensureSharingLink(document, linkAccess)
267
+
268
+ expect(findLinksByDoctype).toHaveBeenCalledWith('io.cozy.files')
269
+ expect(findLinksByIds).not.toHaveBeenCalled()
270
+ expect(provider.updateDocumentPermissions).toHaveBeenCalledWith(
271
+ { ...document, id: 'file-id' },
272
+ expect.any(Object),
273
+ [permission]
274
+ )
275
+ })
276
+
277
+ it('updates an existing link and returns its public URL', async () => {
278
+ const client = createMockClient({})
279
+ client.getStackClient = () => ({ uri: 'https://cozy.example' })
280
+ client.collection = jest.fn().mockReturnValue({})
281
+ client.capabilities = { flat_subdomains: true }
282
+ const provider = setupProvider(client, { documentType: 'Files' })
283
+ provider.fetchSharingLinks = jest.fn().mockResolvedValue([permission])
284
+ provider.updateDocumentPermissions = jest
285
+ .fn()
286
+ .mockResolvedValue([{ data: permission }])
287
+
288
+ const result = await provider.ensureSharingLink(document, linkAccess)
289
+
290
+ expect(provider.updateDocumentPermissions).toHaveBeenCalledWith(
291
+ { ...document, id: 'file-id' },
292
+ {
293
+ verbs: ['GET', 'POST', 'PUT', 'PATCH'],
294
+ expiresAt: '',
295
+ password: ''
296
+ },
297
+ [permission]
298
+ )
299
+ expect(result.documentId).toBe('file-id')
300
+ expect(result.url).toContain('sharecode=share-code')
301
+ })
302
+
303
+ it('creates a link when no existing permission is found', async () => {
304
+ const client = createMockClient({})
305
+ client.getStackClient = () => ({ uri: 'https://cozy.example' })
306
+ client.collection = jest.fn().mockReturnValue({})
307
+ client.capabilities = { flat_subdomains: true }
308
+ const provider = setupProvider(client, { documentType: 'Files' })
309
+ provider.fetchSharingLinks = jest.fn().mockResolvedValue([])
310
+ provider.shareByLink = jest.fn().mockResolvedValue({ data: permission })
311
+
312
+ await provider.ensureSharingLink(document, linkAccess)
313
+
314
+ expect(provider.shareByLink).toHaveBeenCalledWith(
315
+ document,
316
+ expect.objectContaining({ verbs: ['GET', 'POST', 'PUT', 'PATCH'] })
317
+ )
318
+ })
319
+
320
+ it('sends the selected expiry and password when updating a link', async () => {
321
+ const client = createMockClient({})
322
+ client.getStackClient = () => ({ uri: 'https://cozy.example' })
323
+ client.collection = jest.fn().mockReturnValue({})
324
+ client.capabilities = { flat_subdomains: true }
325
+ const provider = setupProvider(client, { documentType: 'Files' })
326
+ const selectedDate = new Date('2100-01-01T12:00:00.000Z')
327
+ provider.fetchSharingLinks = jest.fn().mockResolvedValue([permission])
328
+ provider.updateDocumentPermissions = jest
329
+ .fn()
330
+ .mockResolvedValue([{ data: permission }])
331
+
332
+ await provider.ensureSharingLink(document, {
333
+ editingRights: 'readOnly',
334
+ dateEnabled: true,
335
+ selectedDate,
336
+ passwordEnabled: true,
337
+ password: 'secret'
338
+ })
339
+
340
+ expect(provider.updateDocumentPermissions).toHaveBeenCalledWith(
341
+ { ...document, id: 'file-id' },
342
+ {
343
+ verbs: ['GET'],
344
+ expiresAt: endOfDay(selectedDate).toISOString(),
345
+ password: 'secret'
346
+ },
347
+ [permission]
348
+ )
349
+ })
350
+
351
+ it('updates permissions discovered outside the current store state', async () => {
352
+ const client = createMockClient({})
353
+ client.getStackClient = () => ({ uri: 'https://cozy.example' })
354
+ client.collection = jest.fn().mockReturnValue({})
355
+ client.capabilities = { flat_subdomains: true }
356
+ const provider = setupProvider(client, { documentType: 'Files' })
357
+ const updatePermission = jest.fn().mockResolvedValue({ data: permission })
358
+ provider.permissionCol = { add: updatePermission }
359
+ provider.fetchSharingLinks = jest.fn().mockResolvedValue([permission])
360
+ provider.dispatch = jest.fn()
361
+
362
+ await provider.ensureSharingLink(document, linkAccess)
363
+
364
+ expect(updatePermission).toHaveBeenCalledTimes(1)
365
+ })
366
+ })
367
+
200
368
  describe('revokeSharingLink', () => {
201
369
  const PERM = {
202
370
  type: 'io.cozy.permissions',
@@ -217,11 +385,11 @@ describe('revokeSharingLink', () => {
217
385
  const mockClient = createMockClient({})
218
386
  mockClient.getStackClient = () => ({ uri: 'http://cozy.local' })
219
387
  mockClient.collection = jest.fn().mockReturnValue({ destroy: mockDestroy })
220
- provider = new SharingProvider({ client: mockClient })
221
- provider.state = reducer(undefined, addSharingLink(PERM))
222
- provider.dispatch = jest.fn(action => {
223
- provider.state = reducer(provider.state, action)
224
- })
388
+ provider = setupProvider(
389
+ mockClient,
390
+ {},
391
+ reducer(undefined, addSharingLink(PERM))
392
+ )
225
393
  })
226
394
 
227
395
  it('uses permissionCol.destroy for a regular file', async () => {
@@ -281,9 +449,12 @@ describe('updateDocumentPermissions', () => {
281
449
  const mockClient = createMockClient({})
282
450
  mockClient.collection = jest.fn().mockReturnValue({ add: mockAdd })
283
451
 
284
- const provider = new SharingProvider({ client: mockClient })
285
452
  // Seed the state with exactly one permission (as it would be after a correct shareByLink)
286
- provider.state = reducer(undefined, addSharingLink(PERM_DRIVE_FILE))
453
+ const provider = setupProvider(
454
+ mockClient,
455
+ {},
456
+ reducer(undefined, addSharingLink(PERM_DRIVE_FILE))
457
+ )
287
458
  provider.dispatch = jest.fn()
288
459
 
289
460
  await provider.updateDocumentPermissions(driveFile, {
@@ -354,17 +525,11 @@ describe('shared drive recipient revocation', () => {
354
525
  mockClient.getStackClient = () => ({ uri: 'http://cozy.local' })
355
526
  mockClient.collection = jest.fn().mockReturnValue({})
356
527
 
357
- provider = new SharingProvider({ client: mockClient })
358
- provider.state = {
359
- ...provider.state,
360
- ...reducer(undefined, receiveSharings({ sharings: [SHARED_DRIVE] }))
361
- }
362
- provider.dispatch = jest.fn(action => {
363
- provider.state = {
364
- ...provider.state,
365
- ...reducer(provider.state, action)
366
- }
367
- })
528
+ provider = setupProvider(
529
+ mockClient,
530
+ {},
531
+ reducer(undefined, receiveSharings({ sharings: [SHARED_DRIVE] }))
532
+ )
368
533
  sharingCol = {
369
534
  addRecipients: jest.fn(),
370
535
  create: jest.fn().mockResolvedValue({ data: NEW_SHARING }),
@@ -437,11 +602,7 @@ describe('fetchSharedDriveSharingLinks', () => {
437
602
  findLinksByIds: mockFindLinksByIds
438
603
  })
439
604
 
440
- const provider = new SharingProvider({ client: mockClient })
441
- provider.state = reducer()
442
- provider.dispatch = jest.fn(action => {
443
- provider.state = reducer(provider.state, action)
444
- })
605
+ const provider = setupProvider(mockClient)
445
606
 
446
607
  const result = await provider.fetchSharedDriveSharingLinks(driveFile)
447
608
 
@@ -463,7 +624,7 @@ describe('fetchSharedDriveSharingLinks', () => {
463
624
  findLinksByIds
464
625
  })
465
626
 
466
- const provider = new SharingProvider({ client: mockClient })
627
+ const provider = setupProvider(mockClient)
467
628
  provider.dispatch = jest.fn()
468
629
 
469
630
  const result = await provider.fetchSharedDriveSharingLinks({
@@ -484,11 +645,7 @@ describe('fetchSharedDriveSharingLinks', () => {
484
645
  findLinksByIds: mockFindLinksByIds
485
646
  })
486
647
 
487
- const provider = new SharingProvider({ client: mockClient })
488
- provider.state = reducer()
489
- provider.dispatch = jest.fn(action => {
490
- provider.state = reducer(provider.state, action)
491
- })
648
+ const provider = setupProvider(mockClient)
492
649
 
493
650
  await provider.fetchSharedDriveSharingLinks({
494
651
  id: 'file_in_drive',
@@ -505,7 +662,7 @@ describe('fetchSharedDriveSharingLinks', () => {
505
662
  findLinksByIds: mockFindLinksByIds
506
663
  })
507
664
 
508
- const provider = new SharingProvider({ client: mockClient })
665
+ const provider = setupProvider(mockClient)
509
666
  provider.dispatch = jest.fn()
510
667
 
511
668
  const result = await provider.fetchSharedDriveSharingLinks(driveFile)
@@ -550,11 +707,7 @@ describe('shareByLink shared drive 409 recovery', () => {
550
707
  findLinksByIds: mockFindLinksByIds
551
708
  })
552
709
 
553
- const provider = new SharingProvider({ client: mockClient })
554
- provider.state = reducer()
555
- provider.dispatch = jest.fn(action => {
556
- provider.state = reducer(provider.state, action)
557
- })
710
+ const provider = setupProvider(mockClient)
558
711
 
559
712
  const resp = await provider.shareByLink(driveFile, { verbs: ['GET'] })
560
713
 
@@ -575,11 +728,11 @@ describe('shareByLink shared drive 409 recovery', () => {
575
728
  findLinksByIds: mockFindLinksByIds
576
729
  })
577
730
 
578
- const provider = new SharingProvider({ client: mockClient })
579
- provider.state = reducer(undefined, addSharingLink(PERM_DRIVE_FILE))
580
- provider.dispatch = jest.fn(action => {
581
- provider.state = reducer(provider.state, action)
582
- })
731
+ const provider = setupProvider(
732
+ mockClient,
733
+ {},
734
+ reducer(undefined, addSharingLink(PERM_DRIVE_FILE))
735
+ )
583
736
 
584
737
  const resp = await provider.shareByLink(driveFile, { verbs: ['GET'] })
585
738
 
@@ -599,8 +752,7 @@ describe('shareByLink shared drive 409 recovery', () => {
599
752
  findLinksByIds: mockFindLinksByIds
600
753
  })
601
754
 
602
- const provider = new SharingProvider({ client: mockClient })
603
- provider.state = reducer()
755
+ const provider = setupProvider(mockClient)
604
756
  provider.dispatch = jest.fn()
605
757
 
606
758
  await expect(
@@ -629,18 +781,18 @@ describe('updateSharingMemberType', () => {
629
781
  collection: jest.fn().mockReturnValue({})
630
782
  }
631
783
 
632
- instance = new SharingProvider({ client: mockClient })
633
- instance.state = {
634
- ...instance.state,
635
- sharings: [mockSharing]
636
- }
784
+ instance = setupProvider(
785
+ mockClient,
786
+ {},
787
+ {
788
+ ...reducer(),
789
+ sharings: [mockSharing]
790
+ }
791
+ )
637
792
  instance.sharingCol = {
638
793
  setReadOnly: jest.fn().mockResolvedValue({}),
639
794
  setReadWrite: jest.fn().mockResolvedValue({})
640
795
  }
641
- instance.dispatch = jest.fn(action => {
642
- instance.state = { ...instance.state, ...reducer(instance.state, action) }
643
- })
644
796
  })
645
797
 
646
798
  it('should throw when sharing is not found', async () => {
@@ -1,6 +1,6 @@
1
- import { Icon, CloudPlusOutlined } from '@linagora/twake-icons'
2
1
  import React, { forwardRef } from 'react'
3
2
 
3
+ import { Icon, CloudPlusOutlined } from '@linagora/twake-icons'
4
4
  import ActionsMenuItem from 'cozy-ui/transpiled/react/ActionsMenu/ActionsMenuItem'
5
5
  import ListItemIcon from 'cozy-ui/transpiled/react/ListItemIcon'
6
6
  import ListItemText from 'cozy-ui/transpiled/react/ListItemText'