cozy-sharing 33.2.0 → 33.2.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.
@@ -0,0 +1,75 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import React from 'react';
3
+ import ShareDialogOnlyByLink from './ShareDialogOnlyByLink';
4
+ import AppLike from '../../test/AppLike';
5
+ var mockSharingLink = null;
6
+ jest.mock('./ShareByLink', function () {
7
+ return function () {
8
+ return /*#__PURE__*/React.createElement("button", null, "Copy link");
9
+ };
10
+ });
11
+ jest.mock('../hooks/useSharingContext', function () {
12
+ return {
13
+ useSharingContext: function useSharingContext() {
14
+ return {
15
+ documentType: 'Files',
16
+ getDocumentPermissions: function getDocumentPermissions() {
17
+ return [];
18
+ },
19
+ getSharingLink: function getSharingLink() {
20
+ return mockSharingLink;
21
+ },
22
+ revokeSharingLink: jest.fn(),
23
+ updateDocumentPermissions: jest.fn()
24
+ };
25
+ }
26
+ };
27
+ });
28
+ describe('ShareDialogOnlyByLink', function () {
29
+ beforeEach(function () {
30
+ mockSharingLink = null;
31
+ });
32
+
33
+ var renderModal = function renderModal() {
34
+ return /*#__PURE__*/React.createElement(AppLike, null, /*#__PURE__*/React.createElement(ShareDialogOnlyByLink, {
35
+ document: {
36
+ _id: 'file-id',
37
+ attributes: {
38
+ name: 'file.txt'
39
+ }
40
+ },
41
+ documentType: "Files",
42
+ isOwner: true,
43
+ onClose: jest.fn(),
44
+ onRevoke: jest.fn(),
45
+ onRevokeSelf: jest.fn(),
46
+ permissions: [],
47
+ recipients: []
48
+ }));
49
+ };
50
+
51
+ var setup = function setup() {
52
+ return render(renderModal());
53
+ };
54
+
55
+ it('shows link access as soon as a link is generated', function () {
56
+ var _setup = setup(),
57
+ rerender = _setup.rerender;
58
+
59
+ expect(screen.queryByText('Anyone with the link')).toBe(null);
60
+ mockSharingLink = 'https://example.com/public';
61
+ rerender(renderModal());
62
+ expect(screen.queryByText('Anyone with the link')).toBeInTheDocument();
63
+ });
64
+ it('hides link access as soon as a link is revoked', function () {
65
+ mockSharingLink = 'https://example.com/public';
66
+
67
+ var _setup2 = setup(),
68
+ rerender = _setup2.rerender;
69
+
70
+ expect(screen.queryByText('Anyone with the link')).toBeInTheDocument();
71
+ mockSharingLink = null;
72
+ rerender(renderModal());
73
+ expect(screen.queryByText('Anyone with the link')).toBe(null);
74
+ });
75
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-sharing",
3
- "version": "33.2.0",
3
+ "version": "33.2.2",
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": "aeaf0cb5bb862ce8908d78862ca79e8e749982f2"
86
+ "gitHead": "d4513639ed420da6b9edfef7cb3230d90d0cf537"
87
87
  }
@@ -1,5 +1,5 @@
1
1
  import PropTypes from 'prop-types'
2
- import React, { useCallback, useEffect, useMemo, useState } from 'react'
2
+ import React, { useCallback, useState } from 'react'
3
3
 
4
4
  import { useClient } from 'cozy-client'
5
5
  import Button from 'cozy-ui/transpiled/react/Buttons'
@@ -31,11 +31,9 @@ const FederatedFolderModalContent = ({
31
31
  const { t } = useI18n()
32
32
  const {
33
33
  share,
34
+ getSharingById,
34
35
  getSharingLink,
35
36
  getFederatedShareLink,
36
- getDocumentPermissions,
37
- getOwner,
38
- getSharingById,
39
37
  getRecipients,
40
38
  hasSharedChild,
41
39
  hasSharedParent,
@@ -44,7 +42,6 @@ const FederatedFolderModalContent = ({
44
42
  const { showAlert } = useAlert()
45
43
  const { showConfirmDialog, closeConfirmDialog } = useConfirmDialog()
46
44
 
47
- const [sharingLink, setSharingLink] = useState(null)
48
45
  const {
49
46
  pendingRecipients,
50
47
  setPendingRecipients,
@@ -98,39 +95,29 @@ const FederatedFolderModalContent = ({
98
95
  t
99
96
  ])
100
97
 
101
- const documentPermissions = useMemo(
102
- () =>
103
- existingDocument ? getDocumentPermissions(existingDocument._id) : [],
104
- [existingDocument, getDocumentPermissions]
105
- )
106
-
107
- useEffect(() => {
108
- const fetchSharingLink = async () => {
109
- if (!existingDocument) return
110
-
111
- if (existingDocument.driveId) {
112
- const link = await getFederatedShareLink(existingDocument)
113
- setSharingLink(link)
114
- } else {
115
- setSharingLink(getSharingLink(existingDocument._id))
116
- }
117
- }
118
-
119
- fetchSharingLink()
120
- }, [
121
- existingDocument,
122
- documentPermissions,
123
- getFederatedShareLink,
124
- getSharingLink,
125
- getOwner,
126
- getSharingById
127
- ])
128
-
129
98
  const folderName = existingDocument?.name || ''
130
99
  const documentPath = existingDocument?.path
131
- const hasParentRestriction = Boolean(
132
- existingDocument?.driveId || (documentPath && hasSharedParent(documentPath))
100
+ const sharedDriveSharing = existingDocument?.driveId
101
+ ? getSharingById(existingDocument.driveId)
102
+ : null
103
+ const sharedDriveRootIds =
104
+ sharedDriveSharing?.attributes?.rules?.reduce(
105
+ (ids, rule) => ids.concat(rule.values || []),
106
+ []
107
+ ) || []
108
+ const isSharedDriveRoot = Boolean(
109
+ existingDocument?.driveId &&
110
+ sharedDriveRootIds.some(
111
+ id => id === existingDocument?._id || id === existingDocument?.id
112
+ )
113
+ )
114
+ const isInsideSharedDrive = Boolean(
115
+ existingDocument?.driveId && !isSharedDriveRoot
116
+ )
117
+ const hasSharedParentByPath = Boolean(
118
+ documentPath && hasSharedParent(documentPath)
133
119
  )
120
+ const hasParentRestriction = isInsideSharedDrive || hasSharedParentByPath
134
121
  const hasChildRestriction = Boolean(
135
122
  documentPath && hasSharedChild(documentPath)
136
123
  )
@@ -185,6 +172,9 @@ const FederatedFolderModalContent = ({
185
172
  const existingRecipients = existingDocument
186
173
  ? getRecipients(existingDocument._id)
187
174
  : []
175
+ const displayedLink = existingDocument?.driveId
176
+ ? getFederatedShareLink(existingDocument)
177
+ : getSharingLink(existingDocument?._id)
188
178
 
189
179
  const modalTitle = t('FederatedFolder.shareTitle', { name: folderName })
190
180
 
@@ -252,14 +242,14 @@ const FederatedFolderModalContent = ({
252
242
  documentType="Files"
253
243
  className="u-w-100"
254
244
  onRevoke={revoke}
255
- link={sharingLink}
245
+ link={displayedLink}
256
246
  />
257
247
  </div>
258
248
  }
259
249
  actions={
260
250
  <>
261
251
  <ShareByLink
262
- link={sharingLink}
252
+ link={displayedLink}
263
253
  document={isSending ? frozenDoc : existingDocument}
264
254
  documentType="Files"
265
255
  showGenerateLinkButton={showGenerateLinkButton}
@@ -9,10 +9,12 @@ import { usePendingRecipients } from '../../hooks/usePendingRecipients'
9
9
  import AppLike from '../SharingBanner/test/AppLike'
10
10
 
11
11
  const mockShare = jest.fn()
12
+ const mockShareByLink = jest.fn()
12
13
  const mockRevoke = jest.fn()
13
14
  const mockGetSharingLink = jest.fn()
14
15
  const mockGetFederatedShareLink = jest.fn()
15
16
  const mockGetRecipients = jest.fn().mockReturnValue([])
17
+ const mockGetSharingById = jest.fn()
16
18
  const mockHasSharedChild = jest.fn()
17
19
  const mockHasSharedParent = jest.fn()
18
20
  const mockShowAlert = jest.fn()
@@ -21,11 +23,14 @@ const mockOnClose = jest.fn()
21
23
  jest.mock('../../hooks/useSharingContext', () => ({
22
24
  useSharingContext: () => ({
23
25
  share: mockShare,
26
+ shareByLink: mockShareByLink,
24
27
  revoke: mockRevoke,
25
28
  getSharingLink: mockGetSharingLink,
26
29
  getFederatedShareLink: mockGetFederatedShareLink,
27
30
  getDocumentPermissions: jest.fn().mockReturnValue([]),
31
+ getOwner: jest.fn(),
28
32
  getRecipients: mockGetRecipients,
33
+ getSharingById: mockGetSharingById,
29
34
  hasSharedChild: mockHasSharedChild,
30
35
  hasSharedParent: mockHasSharedParent
31
36
  })
@@ -47,6 +52,8 @@ jest.mock('../../hooks/usePendingRecipients', () => ({
47
52
  usePendingRecipients: jest.fn()
48
53
  }))
49
54
 
55
+ jest.mock('../ShareByLink', () => () => <button>Copy link</button>)
56
+
50
57
  const mockDocument = {
51
58
  _id: 'folder-123',
52
59
  name: 'My Test Folder',
@@ -90,12 +97,26 @@ describe('FederatedFolderModal', () => {
90
97
  beforeEach(() => {
91
98
  jest.clearAllMocks()
92
99
  mockGetSharingLink.mockReturnValue('https://example.com/share/abc123')
93
- mockGetFederatedShareLink.mockResolvedValue(
100
+ mockGetFederatedShareLink.mockReturnValue(
94
101
  'https://example.com/share/federated-abc123'
95
102
  )
96
103
  mockGetRecipients.mockReturnValue([])
104
+ mockGetSharingById.mockReturnValue(null)
97
105
  mockHasSharedChild.mockReturnValue(false)
98
106
  mockHasSharedParent.mockReturnValue(false)
107
+ mockShareByLink.mockResolvedValue({
108
+ data: {
109
+ _id: 'permission-id',
110
+ attributes: {
111
+ shortcodes: { code: 'abc123' }
112
+ }
113
+ }
114
+ })
115
+ Object.assign(navigator, {
116
+ clipboard: {
117
+ writeText: jest.fn()
118
+ }
119
+ })
99
120
  client = createTestClient()
100
121
  sharingContextValue = createSharingContextValue()
101
122
  usePendingRecipients.mockReturnValue({
@@ -139,6 +160,22 @@ describe('FederatedFolderModal', () => {
139
160
  await findByText('Copy link')
140
161
  })
141
162
 
163
+ it('should show link access as soon as a link is generated', async () => {
164
+ mockGetSharingLink.mockReturnValue(null)
165
+ const { findByText, queryByText, rerender } = setup()
166
+
167
+ expect(queryByText('Anyone with the link')).toBe(null)
168
+
169
+ mockGetSharingLink.mockReturnValue('https://example.com/share/abc123')
170
+ rerender(
171
+ <AppLike client={client} sharingContextValue={sharingContextValue}>
172
+ <FederatedFolderModal document={mockDocument} onClose={mockOnClose} />
173
+ </AppLike>
174
+ )
175
+
176
+ await findByText('Anyone with the link')
177
+ })
178
+
142
179
  it('should hide share by email when document is in federated shared folder received by current user', async () => {
143
180
  const { findByText, queryByText } = setup({
144
181
  document: { ...mockDocument, driveId: 'federated-folder-id' }
@@ -162,6 +199,32 @@ describe('FederatedFolderModal', () => {
162
199
  await findByText('This folder can only be shared by link, because')
163
200
  await findByText('it has a shared parent')
164
201
  })
202
+
203
+ it('should allow share by email when document is the federated shared folder root', async () => {
204
+ mockGetSharingById.mockReturnValue({
205
+ attributes: {
206
+ rules: [
207
+ { values: ['another-folder-id'] },
208
+ { values: ['yet-another-folder-id', mockDocument._id] }
209
+ ]
210
+ }
211
+ })
212
+
213
+ const { findByText, queryByText } = setup({
214
+ document: {
215
+ ...mockDocument,
216
+ driveId: 'federated-folder-id',
217
+ type: 'directory'
218
+ }
219
+ })
220
+
221
+ await findByText('Add users')
222
+
223
+ expect(
224
+ queryByText('This folder can only be shared by link, because')
225
+ ).toBe(null)
226
+ expect(queryByText('it has a shared parent')).toBe(null)
227
+ })
165
228
  })
166
229
 
167
230
  describe('onSend callback', () => {
@@ -19,6 +19,7 @@ import { getOrCreateFromArray } from '../helpers/contacts'
19
19
  import { hasReachRecipientsLimit } from '../helpers/recipients'
20
20
  import { getErrorMessage, getSuccessMessage } from '../helpers/share'
21
21
  import { usePendingRecipients } from '../hooks/usePendingRecipients'
22
+ import { useSharingContext } from '../hooks/useSharingContext'
22
23
  import styles from '../styles/share.styl'
23
24
 
24
25
  const SharingContent = ({
@@ -126,6 +127,7 @@ const ShareDialogCozyToCozy = ({
126
127
  const { t } = useI18n()
127
128
  const client = useClient()
128
129
  const { showAlert } = useAlert()
130
+ const { getSharingLink } = useSharingContext()
129
131
 
130
132
  const {
131
133
  pendingRecipients,
@@ -136,6 +138,7 @@ const ShareDialogCozyToCozy = ({
136
138
 
137
139
  const [submitting, setSubmitting] = useState(false)
138
140
  const [showRecipientsLimit, setShowRecipientsLimit] = useState(false)
141
+ const displayedLink = getSharingLink(document._id || document.id)
139
142
 
140
143
  const handleShare = useCallback(async () => {
141
144
  if (pendingRecipients.length === 0) {
@@ -238,6 +241,7 @@ const ShareDialogCozyToCozy = ({
238
241
  <>
239
242
  <ShareDialogTwoStepsConfirmationContainer
240
243
  {...props}
244
+ link={displayedLink}
241
245
  documentType={documentType}
242
246
  document={document}
243
247
  recipients={recipients}
@@ -16,6 +16,7 @@ jest.mock('cozy-client', () => ({
16
16
 
17
17
  jest.mock('../hooks/useSharingContext', () => ({
18
18
  useSharingContext: () => ({
19
+ getSharingLink: jest.fn(() => null),
19
20
  updateSharingMemberType: jest.fn()
20
21
  })
21
22
  }))
@@ -9,6 +9,7 @@ import { useI18n } from 'twake-i18n'
9
9
  import AntivirusAlert from './AntivirusAlert'
10
10
  import { default as DumbShareByLink } from './ShareByLink'
11
11
  import WhoHasAccess from './WhoHasAccess'
12
+ import { useSharingContext } from '../hooks/useSharingContext'
12
13
 
13
14
  const ShareDialogOnlyByLink = ({
14
15
  isOwner,
@@ -17,7 +18,6 @@ const ShareDialogOnlyByLink = ({
17
18
  recipients,
18
19
  document,
19
20
  documentType,
20
- link,
21
21
  onClose,
22
22
  permissions,
23
23
  showGenerateLinkButton,
@@ -25,6 +25,8 @@ const ShareDialogOnlyByLink = ({
25
25
  }) => {
26
26
  const { t } = useI18n()
27
27
  const { isMobile } = useBreakpoints()
28
+ const { getSharingLink } = useSharingContext()
29
+ const displayedLink = getSharingLink(document._id || document.id)
28
30
 
29
31
  return (
30
32
  <Dialog
@@ -51,7 +53,7 @@ const ShareDialogOnlyByLink = ({
51
53
  onRevoke={onRevoke}
52
54
  onRevokeSelf={onRevokeSelf}
53
55
  recipients={recipients}
54
- link={link}
56
+ link={displayedLink}
55
57
  permissions={permissions}
56
58
  />
57
59
  <div
@@ -61,7 +63,7 @@ const ShareDialogOnlyByLink = ({
61
63
  )}
62
64
  >
63
65
  <DumbShareByLink
64
- link={link}
66
+ link={displayedLink}
65
67
  document={document}
66
68
  documentType={documentType}
67
69
  showGenerateLinkButton={showGenerateLinkButton}
@@ -0,0 +1,65 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import React from 'react'
3
+
4
+ import ShareDialogOnlyByLink from './ShareDialogOnlyByLink'
5
+ import AppLike from '../../test/AppLike'
6
+
7
+ let mockSharingLink = null
8
+
9
+ jest.mock('./ShareByLink', () => () => <button>Copy link</button>)
10
+
11
+ jest.mock('../hooks/useSharingContext', () => ({
12
+ useSharingContext: () => ({
13
+ documentType: 'Files',
14
+ getDocumentPermissions: () => [],
15
+ getSharingLink: () => mockSharingLink,
16
+ revokeSharingLink: jest.fn(),
17
+ updateDocumentPermissions: jest.fn()
18
+ })
19
+ }))
20
+
21
+ describe('ShareDialogOnlyByLink', () => {
22
+ beforeEach(() => {
23
+ mockSharingLink = null
24
+ })
25
+
26
+ const renderModal = () => (
27
+ <AppLike>
28
+ <ShareDialogOnlyByLink
29
+ document={{ _id: 'file-id', attributes: { name: 'file.txt' } }}
30
+ documentType="Files"
31
+ isOwner
32
+ onClose={jest.fn()}
33
+ onRevoke={jest.fn()}
34
+ onRevokeSelf={jest.fn()}
35
+ permissions={[]}
36
+ recipients={[]}
37
+ />
38
+ </AppLike>
39
+ )
40
+
41
+ const setup = () => render(renderModal())
42
+
43
+ it('shows link access as soon as a link is generated', () => {
44
+ const { rerender } = setup()
45
+
46
+ expect(screen.queryByText('Anyone with the link')).toBe(null)
47
+
48
+ mockSharingLink = 'https://example.com/public'
49
+ rerender(renderModal())
50
+
51
+ expect(screen.queryByText('Anyone with the link')).toBeInTheDocument()
52
+ })
53
+
54
+ it('hides link access as soon as a link is revoked', () => {
55
+ mockSharingLink = 'https://example.com/public'
56
+ const { rerender } = setup()
57
+
58
+ expect(screen.queryByText('Anyone with the link')).toBeInTheDocument()
59
+
60
+ mockSharingLink = null
61
+ rerender(renderModal())
62
+
63
+ expect(screen.queryByText('Anyone with the link')).toBe(null)
64
+ })
65
+ })