cozy-sharing 40.1.7 → 40.1.8

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 CHANGED
@@ -3,6 +3,10 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [40.1.8](https://github.com/cozy/cozy-libs/compare/cozy-sharing@40.1.7...cozy-sharing@40.1.8) (2026-10-27)
7
+
8
+ **Note:** Version bump only for package cozy-sharing
9
+
6
10
  ## [40.1.7](https://github.com/cozy/cozy-libs/compare/cozy-sharing@40.1.6...cozy-sharing@40.1.7) (2026-10-27)
7
11
 
8
12
  **Note:** Version bump only for package cozy-sharing
@@ -80,9 +80,9 @@ jest.mock('../../hoc/withLocales', function () {
80
80
  };
81
81
  });
82
82
  jest.mock('../../helpers/contacts', function () {
83
- return {
83
+ return _objectSpread(_objectSpread({}, jest.requireActual('../../helpers/contacts')), {}, {
84
84
  getOrCreateFromArray: jest.fn()
85
- };
85
+ });
86
86
  });
87
87
  jest.mock('../../hooks/usePendingRecipients', function () {
88
88
  return {
@@ -1,4 +1,3 @@
1
- import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
3
2
 
4
3
  function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
@@ -8,23 +7,17 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
8
7
  import PropTypes from 'prop-types';
9
8
  import React from 'react';
10
9
  import { useQueryAll, isQueryLoading } from 'cozy-client';
11
- import { models } from 'cozy-client';
12
10
  import flag from 'cozy-flags';
13
11
  import ShareAutosuggest from './ShareAutosuggest';
14
- import { getContactsFromGroupId } from '../helpers/contacts';
12
+ import { computeContactsAndGroups } from '../helpers/contacts';
15
13
  import { buildReachableContactsQuery, buildContactGroupsQuery, buildUnreachableContactsWithGroupsQuery } from '../queries/queries';
16
- var ContactModel = models.contact;
17
14
  /**
18
- * ShareRecipientsInput is responsible for fetching contacts and groups.
15
+ * ShareAutosuggestWrapper is responsible for fetching contacts and groups.
19
16
  * We retrieve all the contacts that are reachable and the groups they belong to.
20
17
  * In order to display the total number of members in each group, we also retrieve the contacts that are not reachable.
21
18
  */
22
19
 
23
- var isCurrentUser = function isCurrentUser(contact) {
24
- return contact.me === true;
25
- };
26
-
27
- var ShareRecipientsInput = function ShareRecipientsInput(_ref) {
20
+ var ShareAutosuggestWrapper = function ShareAutosuggestWrapper(_ref) {
28
21
  var currentRecipients = _ref.currentRecipients,
29
22
  recipients = _ref.recipients,
30
23
  placeholder = _ref.placeholder,
@@ -43,48 +36,13 @@ var ShareRecipientsInput = function ShareRecipientsInput(_ref) {
43
36
  var contactGroupsQuery = buildContactGroupsQuery();
44
37
  var contactGroupsResult = useQueryAll(contactGroupsQuery.definition, contactGroupsQuery.options);
45
38
  var isLoading = isQueryLoading(reachableContactsResult) || reachableContactsResult.hasMore || isQueryLoading(contactGroupsResult) || contactGroupsResult.hasMore || flag('sharing.show-recipient-groups') && (isQueryLoading(unreachableContactsWithGroupsResult) || unreachableContactsWithGroupsResult.hasMore);
46
- var reachableContacts = (reachableContactsResult.data || []).filter(function (contact) {
47
- return !isCurrentUser(contact) && // We do not want contact already in the sharing
48
- !currentRecipients.find(function (r) {
49
- return r.email === ContactModel.getPrimaryEmail(contact);
50
- }) && // We do not want contact currently in pending contacts (in chips)
51
- !recipients.find(function (r) {
52
- return r._id === contact._id;
53
- });
54
- });
55
- var unreachableContactsWithGroups = (unreachableContactsWithGroupsResult.data || []).filter(function (contact) {
56
- return !isCurrentUser(contact);
57
- });
58
- var currentRecipientGroups = currentRecipients.map(function (_ref2) {
59
- var id = _ref2.id;
60
- return id;
61
- }).filter(Boolean);
62
- var contactGroups = (contactGroupsResult.data || []).filter(function (_ref3) {
63
- var _id = _ref3._id;
64
- return !flag('sharing.show-recipient-groups') || !currentRecipientGroups.includes(_id);
65
- }).map(function (contactGroup) {
66
- var reachableMembers = getContactsFromGroupId(reachableContacts, contactGroup.id).map(function (contact) {
67
- return _objectSpread(_objectSpread({}, contact), {}, {
68
- isReachable: true
69
- });
70
- });
71
-
72
- if (flag('sharing.show-recipient-groups') !== true) {
73
- return _objectSpread(_objectSpread({}, contactGroup), {}, {
74
- members: reachableMembers
75
- });
76
- }
77
-
78
- var unreachableMembers = getContactsFromGroupId(unreachableContactsWithGroups, contactGroup.id).map(function (contact) {
79
- return _objectSpread(_objectSpread({}, contact), {}, {
80
- isReachable: false
81
- });
82
- });
83
- return _objectSpread(_objectSpread({}, contactGroup), {}, {
84
- members: [].concat(_toConsumableArray(reachableMembers), _toConsumableArray(unreachableMembers))
85
- });
39
+ var contactsAndGroups = computeContactsAndGroups({
40
+ reachableContacts: reachableContactsResult.data || [],
41
+ unreachableContactsWithGroups: unreachableContactsWithGroupsResult.data || [],
42
+ contactGroups: contactGroupsResult.data || [],
43
+ currentRecipients: currentRecipients,
44
+ pendingRecipients: recipients
86
45
  });
87
- var contactsAndGroups = [].concat(_toConsumableArray(reachableContacts), _toConsumableArray(contactGroups));
88
46
  return /*#__PURE__*/React.createElement(ShareAutosuggest, {
89
47
  loading: isLoading,
90
48
  disabled: disabled,
@@ -99,7 +57,7 @@ var ShareRecipientsInput = function ShareRecipientsInput(_ref) {
99
57
  });
100
58
  };
101
59
 
102
- ShareRecipientsInput.propTypes = {
60
+ ShareAutosuggestWrapper.propTypes = {
103
61
  currentRecipients: PropTypes.array,
104
62
  recipients: PropTypes.array,
105
63
  placeholder: PropTypes.string,
@@ -110,8 +68,8 @@ ShareRecipientsInput.propTypes = {
110
68
  autoFocus: PropTypes.bool,
111
69
  endAdornment: PropTypes.node
112
70
  };
113
- ShareRecipientsInput.defaultProps = {
71
+ ShareAutosuggestWrapper.defaultProps = {
114
72
  currentRecipients: [],
115
73
  recipients: []
116
74
  };
117
- export default ShareRecipientsInput;
75
+ export default ShareAutosuggestWrapper;
@@ -4,7 +4,7 @@ import { render, screen } from '@testing-library/react';
4
4
  import React from 'react';
5
5
  import { createMockClient } from 'cozy-client';
6
6
  import flag from 'cozy-flags';
7
- import ShareRecipientsInput from './ShareRecipientsInput';
7
+ import ShareAutosuggestWrapper from './ShareAutosuggestWrapper';
8
8
  import AppLike from './SharingBanner/test/AppLike';
9
9
  jest.mock('cozy-flags', function () {
10
10
  return {
@@ -33,7 +33,7 @@ jest.mock('./ShareAutosuggest', function () {
33
33
  }
34
34
  };
35
35
  });
36
- describe('ShareRecipientsInput component', function () {
36
+ describe('ShareAutosuggestWrapper component', function () {
37
37
  var setup = function setup(_ref2) {
38
38
  var contacts = _ref2.contacts,
39
39
  contactGroups = _ref2.contactGroups,
@@ -61,7 +61,7 @@ describe('ShareRecipientsInput component', function () {
61
61
  });
62
62
  render( /*#__PURE__*/React.createElement(AppLike, {
63
63
  client: mockClient
64
- }, /*#__PURE__*/React.createElement(ShareRecipientsInput, {
64
+ }, /*#__PURE__*/React.createElement(ShareAutosuggestWrapper, {
65
65
  onPick: function onPick() {},
66
66
  onRemove: function onRemove() {},
67
67
  placeholder: "Enter recipients here",
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
3
3
  import React from 'react';
4
4
  import flag from 'cozy-flags';
5
5
  import { useI18n } from 'twake-i18n';
6
- import ShareRecipientsInput from './ShareRecipientsInput';
6
+ import ShareAutosuggestWrapper from './ShareAutosuggestWrapper';
7
7
  import ShareTypeSelect from './Sharetypeselect';
8
8
  import { mergeRecipients, spreadGroupAndMergeRecipients } from '../helpers/recipients';
9
9
  import { isReadOnlySharing } from '../state';
@@ -69,7 +69,7 @@ export var ShareByEmail = function ShareByEmail(_ref) {
69
69
  className: styles['coz-form-group']
70
70
  }, /*#__PURE__*/React.createElement("div", {
71
71
  className: styles['coz-form']
72
- }, /*#__PURE__*/React.createElement(ShareRecipientsInput, {
72
+ }, /*#__PURE__*/React.createElement(ShareAutosuggestWrapper, {
73
73
  placeholder: pendingRecipients.length === 0 ? t("".concat(documentType, ".share.shareByEmail.emailPlaceholder")) : '',
74
74
  onPick: onRecipientPick,
75
75
  onRemove: onRecipientRemove,
@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
3
3
  import React from 'react';
4
4
  import snarkdown from 'snarkdown';
5
5
  import { Icon, SyncCozy, ToTheCloud } from '@linagora/twake-icons';
6
- import { useClient } from 'cozy-client';
7
6
  import flag from 'cozy-flags';
8
7
  import Alert from 'cozy-ui/transpiled/react/Alert';
9
8
  import Avatar from 'cozy-ui/transpiled/react/Avatar';
@@ -20,14 +19,13 @@ var getPublicNameFromSharing = function getPublicNameFromSharing(sharing) {
20
19
  };
21
20
 
22
21
  var PublicBannerCozyToCozyContent = function PublicBannerCozyToCozyContent(_ref) {
23
- var sharing = _ref.sharing;
22
+ var sharing = _ref.sharing,
23
+ avatarURL = _ref.avatarURL;
24
24
 
25
25
  var _useI18n = useI18n(),
26
26
  t = _useI18n.t;
27
27
 
28
- var client = useClient();
29
28
  var name = getPublicNameFromSharing(sharing);
30
- var avatarURL = "".concat(client.options.uri, "/public/avatar?fallback=initials");
31
29
  var withAvatar = snarkdown(t('Share.banner.shared_from', {
32
30
  name: name,
33
31
  image: avatarURL
@@ -48,7 +46,8 @@ var PublicBannerCozyToCozyContent = function PublicBannerCozyToCozyContent(_ref)
48
46
  };
49
47
 
50
48
  PublicBannerCozyToCozyContent.propTypes = {
51
- sharing: PropTypes.object.isRequired
49
+ sharing: PropTypes.object.isRequired,
50
+ avatarURL: PropTypes.string.isRequired
52
51
  };
53
52
 
54
53
  var openExternalLink = function openExternalLink(url) {
@@ -57,6 +56,7 @@ var openExternalLink = function openExternalLink(url) {
57
56
 
58
57
  var SharingBannerCozyToCozy = function SharingBannerCozyToCozy(_ref2) {
59
58
  var sharing = _ref2.sharing,
59
+ avatarURL = _ref2.avatarURL,
60
60
  isSharingShortcutCreated = _ref2.isSharingShortcutCreated,
61
61
  addSharingLink = _ref2.addSharingLink,
62
62
  onClose = _ref2.onClose;
@@ -101,7 +101,8 @@ var SharingBannerCozyToCozy = function SharingBannerCozyToCozy(_ref2) {
101
101
  onClick: onClose
102
102
  }))
103
103
  }, /*#__PURE__*/React.createElement(PublicBannerCozyToCozyContent, {
104
- sharing: sharing
104
+ sharing: sharing,
105
+ avatarURL: avatarURL
105
106
  }));
106
107
  };
107
108
 
@@ -168,6 +169,7 @@ SharingBannerByLink.propTypes = {
168
169
  };
169
170
  SharingBannerCozyToCozy.propTypes = {
170
171
  sharing: PropTypes.object.isRequired,
172
+ avatarURL: PropTypes.string.isRequired,
171
173
  isSharingShortcutCreated: PropTypes.bool.isRequired,
172
174
  addSharingLink: PropTypes.string.isRequired,
173
175
  onClose: PropTypes.func.isRequired
@@ -1,19 +1,10 @@
1
1
  import { render } from '@testing-library/react';
2
2
  import React from 'react';
3
- import { useClient } from 'cozy-client';
4
3
  import { BreakpointsProvider } from 'cozy-ui/transpiled/react/providers/Breakpoints';
5
4
  import { useI18n } from 'twake-i18n';
6
5
  import { SharingBannerCozyToCozy } from './PublicBanner';
7
6
  jest.mock('twake-i18n');
8
- jest.mock('cozy-client');
9
7
  describe('PublicBanner', function () {
10
- beforeEach(function () {
11
- useClient.mockReturnValue({
12
- options: {
13
- uri: 'http://cozy.localhost:8080'
14
- }
15
- });
16
- });
17
8
  it('should not add dangerous html', function () {
18
9
  // Given
19
10
  useI18n.mockReturnValue({
@@ -36,6 +27,7 @@ describe('PublicBanner', function () {
36
27
 
37
28
  var _render = render( /*#__PURE__*/React.createElement(BreakpointsProvider, null, /*#__PURE__*/React.createElement(SharingBannerCozyToCozy, {
38
29
  sharing: sharing,
30
+ avatarURL: "http://cozy.localhost:8080/public/avatar?fallback=initials",
39
31
  isSharingShortcutCreated: true,
40
32
  addSharingLink: "discoveryLink",
41
33
  onClose: function onClose() {}
@@ -4,6 +4,7 @@ import React, { useState, useCallback } from 'react';
4
4
  import { SharingBannerByLink, SharingBannerCozyToCozy } from './PublicBanner';
5
5
  export var SharingBanner = function SharingBanner(_ref) {
6
6
  var sharingInfos = _ref.sharingInfos,
7
+ avatarURL = _ref.avatarURL,
7
8
  hideCreateCozyAction = _ref.hideCreateCozyAction;
8
9
 
9
10
  var _useState = useState(true),
@@ -25,12 +26,14 @@ export var SharingBanner = function SharingBanner(_ref) {
25
26
  }) : /*#__PURE__*/React.createElement(SharingBannerCozyToCozy, {
26
27
  isSharingShortcutCreated: isSharingShortcutCreated,
27
28
  sharing: sharing,
29
+ avatarURL: avatarURL,
28
30
  addSharingLink: addSharingLink,
29
31
  onClose: onClose
30
32
  }));
31
33
  };
32
34
  SharingBanner.propTypes = {
33
35
  sharingInfos: PropTypes.object,
36
+ avatarURL: PropTypes.string,
34
37
  hideCreateCozyAction: PropTypes.bool
35
38
  };
36
39
  export default SharingBanner;
@@ -1,5 +1,6 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React from 'react';
3
+ import { useClient } from 'cozy-client';
3
4
  import { SharingBanner } from './components/SharingBanner';
4
5
  import { useSharingInfos } from './hooks/useSharingInfos';
5
6
  import withLocales from '../../hoc/withLocales';
@@ -7,9 +8,11 @@ import withLocales from '../../hoc/withLocales';
7
8
  var Plugin = function Plugin(_ref) {
8
9
  var previewPath = _ref.previewPath,
9
10
  hideCreateCozyAction = _ref.hideCreateCozyAction;
11
+ var client = useClient();
10
12
  var sharingInfos = useSharingInfos(previewPath);
11
13
  return /*#__PURE__*/React.createElement(SharingBanner, {
12
14
  sharingInfos: sharingInfos,
15
+ avatarURL: "".concat(client.options.uri, "/public/avatar?fallback=initials"),
13
16
  hideCreateCozyAction: hideCreateCozyAction
14
17
  });
15
18
  };
@@ -1,6 +1,16 @@
1
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
2
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
1
3
  import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
4
+
5
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
6
+
7
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
8
+
2
9
  import _regeneratorRuntime from "@babel/runtime/regenerator";
3
- import { validateEmail } from '../helpers/email'; // Return the contact object if present
10
+ import { models } from 'cozy-client';
11
+ import flag from 'cozy-flags';
12
+ import { validateEmail } from '../helpers/email';
13
+ var ContactModel = models.contact; // Return the contact object if present
4
14
  // Else check if a contact exist with this email address, if yes return it
5
15
  // if not create it
6
16
 
@@ -114,4 +124,72 @@ export var getContactsFromGroupId = function getContactsFromGroupId() {
114
124
 
115
125
  export var isContactToBeCreated = function isContactToBeCreated(contact) {
116
126
  return !contact._id;
127
+ };
128
+
129
+ var isCurrentUser = function isCurrentUser(contact) {
130
+ return contact.me === true;
131
+ };
132
+ /**
133
+ * Computes the contacts and groups to suggest when adding recipients to a
134
+ * sharing. The unreachable contacts only count in the members of the groups,
135
+ * when flag sharing.show-recipient-groups is enabled.
136
+ *
137
+ * @param {object} params
138
+ * @param {object[]} params.reachableContacts - Contacts that can be shared with
139
+ * @param {object[]} params.unreachableContactsWithGroups - Contacts that cannot be shared with but belong to a group
140
+ * @param {object[]} params.contactGroups - Groups of contacts
141
+ * @param {object[]} params.currentRecipients - Recipients already in the sharing
142
+ * @param {object[]} params.pendingRecipients - Recipients picked but not yet shared with
143
+ * @returns {object[]} The contacts, then the groups with their members
144
+ */
145
+
146
+
147
+ export var computeContactsAndGroups = function computeContactsAndGroups(_ref3) {
148
+ var reachableContacts = _ref3.reachableContacts,
149
+ unreachableContactsWithGroups = _ref3.unreachableContactsWithGroups,
150
+ contactGroups = _ref3.contactGroups,
151
+ currentRecipients = _ref3.currentRecipients,
152
+ pendingRecipients = _ref3.pendingRecipients;
153
+ var suggestedContacts = reachableContacts.filter(function (contact) {
154
+ return !isCurrentUser(contact) && // We do not want contact already in the sharing
155
+ !currentRecipients.find(function (r) {
156
+ return r.email === ContactModel.getPrimaryEmail(contact);
157
+ }) && // We do not want contact currently in pending contacts (in chips)
158
+ !pendingRecipients.find(function (r) {
159
+ return r._id === contact._id;
160
+ });
161
+ });
162
+ var unreachableContacts = unreachableContactsWithGroups.filter(function (contact) {
163
+ return !isCurrentUser(contact);
164
+ });
165
+ var currentRecipientGroups = currentRecipients.map(function (_ref4) {
166
+ var id = _ref4.id;
167
+ return id;
168
+ }).filter(Boolean);
169
+ var suggestedGroups = contactGroups.filter(function (_ref5) {
170
+ var _id = _ref5._id;
171
+ return !flag('sharing.show-recipient-groups') || !currentRecipientGroups.includes(_id);
172
+ }).map(function (contactGroup) {
173
+ var reachableMembers = getContactsFromGroupId(suggestedContacts, contactGroup.id).map(function (contact) {
174
+ return _objectSpread(_objectSpread({}, contact), {}, {
175
+ isReachable: true
176
+ });
177
+ });
178
+
179
+ if (flag('sharing.show-recipient-groups') !== true) {
180
+ return _objectSpread(_objectSpread({}, contactGroup), {}, {
181
+ members: reachableMembers
182
+ });
183
+ }
184
+
185
+ var unreachableMembers = getContactsFromGroupId(unreachableContacts, contactGroup.id).map(function (contact) {
186
+ return _objectSpread(_objectSpread({}, contact), {}, {
187
+ isReachable: false
188
+ });
189
+ });
190
+ return _objectSpread(_objectSpread({}, contactGroup), {}, {
191
+ members: [].concat(_toConsumableArray(reachableMembers), _toConsumableArray(unreachableMembers))
192
+ });
193
+ });
194
+ return [].concat(_toConsumableArray(suggestedContacts), _toConsumableArray(suggestedGroups));
117
195
  };
@@ -1,6 +1,22 @@
1
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
1
3
  import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
4
+
5
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
6
+
7
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
8
+
2
9
  import _regeneratorRuntime from "@babel/runtime/regenerator";
3
- import { getOrCreateFromArray } from './contacts';
10
+ import flag from 'cozy-flags';
11
+ import { computeContactsAndGroups, getOrCreateFromArray } from './contacts';
12
+ jest.mock('cozy-flags', function () {
13
+ return {
14
+ __esModule: true,
15
+ default: jest.fn(function () {
16
+ return null;
17
+ })
18
+ };
19
+ });
4
20
  var findMock = jest.fn().mockReturnValueOnce({
5
21
  data: [{
6
22
  id: 2,
@@ -63,4 +79,109 @@ describe('getOrCreateFromArray', function () {
63
79
  }
64
80
  }, _callee);
65
81
  })));
82
+ });
83
+ describe('computeContactsAndGroups', function () {
84
+ var group = {
85
+ id: 'friends',
86
+ _id: 'friends',
87
+ name: 'Friends'
88
+ };
89
+ var inGroup = {
90
+ relationships: {
91
+ groups: {
92
+ data: [{
93
+ _id: 'friends'
94
+ }]
95
+ }
96
+ }
97
+ };
98
+
99
+ var alice = _objectSpread({
100
+ _id: 'alice',
101
+ email: [{
102
+ address: 'alice@cozy.cc',
103
+ primary: true
104
+ }]
105
+ }, inGroup);
106
+
107
+ var bob = _objectSpread({
108
+ _id: 'bob',
109
+ email: [{
110
+ address: 'bob@cozy.cc'
111
+ }]
112
+ }, inGroup);
113
+
114
+ var me = _objectSpread({
115
+ _id: 'me',
116
+ me: true
117
+ }, inGroup);
118
+
119
+ var carol = _objectSpread({
120
+ _id: 'carol'
121
+ }, inGroup);
122
+
123
+ var setup = function setup() {
124
+ var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
125
+ _ref2$currentRecipien = _ref2.currentRecipients,
126
+ currentRecipients = _ref2$currentRecipien === void 0 ? [] : _ref2$currentRecipien,
127
+ _ref2$pendingRecipien = _ref2.pendingRecipients,
128
+ pendingRecipients = _ref2$pendingRecipien === void 0 ? [] : _ref2$pendingRecipien,
129
+ _ref2$showRecipientGr = _ref2.showRecipientGroups,
130
+ showRecipientGroups = _ref2$showRecipientGr === void 0 ? null : _ref2$showRecipientGr;
131
+
132
+ flag.mockReturnValue(showRecipientGroups);
133
+ return computeContactsAndGroups({
134
+ reachableContacts: [alice, bob, me],
135
+ unreachableContactsWithGroups: [carol, me],
136
+ contactGroups: [group],
137
+ currentRecipients: currentRecipients,
138
+ pendingRecipients: pendingRecipients
139
+ });
140
+ };
141
+
142
+ it('suggests the contacts, then the groups with their reachable members', function () {
143
+ var _setup = setup(),
144
+ _setup2 = _slicedToArray(_setup, 3),
145
+ first = _setup2[0],
146
+ second = _setup2[1],
147
+ friends = _setup2[2];
148
+
149
+ expect([first._id, second._id, friends._id]).toEqual(['alice', 'bob', 'friends']);
150
+ expect(friends.members.map(function (m) {
151
+ return [m._id, m.isReachable];
152
+ })).toEqual([['alice', true], ['bob', true]]);
153
+ });
154
+ it('does not suggest the recipients already shared with or picked', function () {
155
+ var contactsAndGroups = setup({
156
+ currentRecipients: [{
157
+ email: 'alice@cozy.cc'
158
+ }],
159
+ pendingRecipients: [bob]
160
+ });
161
+ expect(contactsAndGroups.map(function (c) {
162
+ return c._id;
163
+ })).toEqual(['friends']);
164
+ });
165
+ it('adds the unreachable members when flag sharing.show-recipient-groups is enabled', function () {
166
+ var _setup3 = setup({
167
+ showRecipientGroups: true
168
+ }),
169
+ _setup4 = _slicedToArray(_setup3, 3),
170
+ friends = _setup4[2];
171
+
172
+ expect(friends.members.map(function (m) {
173
+ return [m._id, m.isReachable];
174
+ })).toEqual([['alice', true], ['bob', true], ['carol', false]]);
175
+ });
176
+ it('does not suggest the groups already in the sharing when flag sharing.show-recipient-groups is enabled', function () {
177
+ var contactsAndGroups = setup({
178
+ currentRecipients: [{
179
+ id: 'friends'
180
+ }],
181
+ showRecipientGroups: true
182
+ });
183
+ expect(contactsAndGroups.map(function (c) {
184
+ return c._id;
185
+ })).toEqual(['alice', 'bob']);
186
+ });
66
187
  });
@@ -20,8 +20,8 @@ flowchart TD
20
20
  SharingContent --> ShareByEmail
21
21
  ConfirmTrustedRecipientsDialog --> DumbConfirmTrustedRecipientsDialog
22
22
  DumbConfirmTrustedRecipientsDialog --> ShareDialogTwoStepsConfirmationContainer
23
- ShareByEmail --> ShareRecipientsInput
24
- ShareRecipientsInput --> ShareAutosuggest
23
+ ShareByEmail --> ShareAutosuggestWrapper
24
+ ShareAutosuggestWrapper --> ShareAutosuggest
25
25
  ```
26
26
 
27
27
  ### Specific case of `ShareDialogTwoStepsConfirmationContainer`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-sharing",
3
- "version": "40.1.7",
3
+ "version": "40.1.8",
4
4
  "description": "Provides sharing login for React applications.",
5
5
  "main": "dist/index.js",
6
6
  "author": "Cozy",
@@ -58,7 +58,7 @@
58
58
  "cozy-intent": "^2.31.1",
59
59
  "cozy-minilog": "^3.10.1",
60
60
  "cozy-ui": "^140.5.0",
61
- "cozy-ui-plus": "^14.1.2",
61
+ "cozy-ui-plus": "^14.1.3",
62
62
  "jest": "30.3.0",
63
63
  "jest-environment-jsdom": "30.3.0",
64
64
  "react": "16.12.0",
@@ -84,5 +84,5 @@
84
84
  "sideEffects": [
85
85
  "*.css"
86
86
  ],
87
- "gitHead": "ff9d90f0000f5446b91840dbb4237b48336c3997"
87
+ "gitHead": "3d97a76d7d6b7a07f27fa4d25b17b26474e1fdc0"
88
88
  }
@@ -61,6 +61,7 @@ jest.mock('cozy-ui/transpiled/react/providers/Alert', () => ({
61
61
  jest.mock('../../hoc/withLocales', () => Component => Component)
62
62
 
63
63
  jest.mock('../../helpers/contacts', () => ({
64
+ ...jest.requireActual('../../helpers/contacts'),
64
65
  getOrCreateFromArray: jest.fn()
65
66
  }))
66
67
 
@@ -2,27 +2,22 @@ import PropTypes from 'prop-types'
2
2
  import React from 'react'
3
3
 
4
4
  import { useQueryAll, isQueryLoading } from 'cozy-client'
5
- import { models } from 'cozy-client'
6
5
  import flag from 'cozy-flags'
7
6
 
8
7
  import ShareAutosuggest from './ShareAutosuggest'
9
- import { getContactsFromGroupId } from '../helpers/contacts'
8
+ import { computeContactsAndGroups } from '../helpers/contacts'
10
9
  import {
11
10
  buildReachableContactsQuery,
12
11
  buildContactGroupsQuery,
13
12
  buildUnreachableContactsWithGroupsQuery
14
13
  } from '../queries/queries'
15
14
 
16
- const ContactModel = models.contact
17
-
18
15
  /**
19
- * ShareRecipientsInput is responsible for fetching contacts and groups.
16
+ * ShareAutosuggestWrapper is responsible for fetching contacts and groups.
20
17
  * We retrieve all the contacts that are reachable and the groups they belong to.
21
18
  * In order to display the total number of members in each group, we also retrieve the contacts that are not reachable.
22
19
  */
23
- const isCurrentUser = contact => contact.me === true
24
-
25
- const ShareRecipientsInput = ({
20
+ const ShareAutosuggestWrapper = ({
26
21
  currentRecipients,
27
22
  recipients,
28
23
  placeholder,
@@ -64,60 +59,14 @@ const ShareRecipientsInput = ({
64
59
  (isQueryLoading(unreachableContactsWithGroupsResult) ||
65
60
  unreachableContactsWithGroupsResult.hasMore))
66
61
 
67
- const reachableContacts = (reachableContactsResult.data || []).filter(
68
- contact =>
69
- !isCurrentUser(contact) &&
70
- // We do not want contact already in the sharing
71
- !currentRecipients.find(
72
- r => r.email === ContactModel.getPrimaryEmail(contact)
73
- ) &&
74
- // We do not want contact currently in pending contacts (in chips)
75
- !recipients.find(r => r._id === contact._id)
76
- )
77
-
78
- const unreachableContactsWithGroups = (
79
- unreachableContactsWithGroupsResult.data || []
80
- ).filter(contact => !isCurrentUser(contact))
81
- const currentRecipientGroups = currentRecipients
82
- .map(({ id }) => id)
83
- .filter(Boolean)
84
- const contactGroups = (contactGroupsResult.data || [])
85
- .filter(
86
- ({ _id }) =>
87
- !flag('sharing.show-recipient-groups') ||
88
- !currentRecipientGroups.includes(_id)
89
- )
90
- .map(contactGroup => {
91
- const reachableMembers = getContactsFromGroupId(
92
- reachableContacts,
93
- contactGroup.id
94
- ).map(contact => ({
95
- ...contact,
96
- isReachable: true
97
- }))
98
-
99
- if (flag('sharing.show-recipient-groups') !== true) {
100
- return {
101
- ...contactGroup,
102
- members: reachableMembers
103
- }
104
- }
105
-
106
- const unreachableMembers = getContactsFromGroupId(
107
- unreachableContactsWithGroups,
108
- contactGroup.id
109
- ).map(contact => ({
110
- ...contact,
111
- isReachable: false
112
- }))
113
-
114
- return {
115
- ...contactGroup,
116
- members: [...reachableMembers, ...unreachableMembers]
117
- }
118
- })
119
-
120
- const contactsAndGroups = [...reachableContacts, ...contactGroups]
62
+ const contactsAndGroups = computeContactsAndGroups({
63
+ reachableContacts: reachableContactsResult.data || [],
64
+ unreachableContactsWithGroups:
65
+ unreachableContactsWithGroupsResult.data || [],
66
+ contactGroups: contactGroupsResult.data || [],
67
+ currentRecipients,
68
+ pendingRecipients: recipients
69
+ })
121
70
 
122
71
  return (
123
72
  <ShareAutosuggest
@@ -135,7 +84,7 @@ const ShareRecipientsInput = ({
135
84
  )
136
85
  }
137
86
 
138
- ShareRecipientsInput.propTypes = {
87
+ ShareAutosuggestWrapper.propTypes = {
139
88
  currentRecipients: PropTypes.array,
140
89
  recipients: PropTypes.array,
141
90
  placeholder: PropTypes.string,
@@ -147,9 +96,9 @@ ShareRecipientsInput.propTypes = {
147
96
  endAdornment: PropTypes.node
148
97
  }
149
98
 
150
- ShareRecipientsInput.defaultProps = {
99
+ ShareAutosuggestWrapper.defaultProps = {
151
100
  currentRecipients: [],
152
101
  recipients: []
153
102
  }
154
103
 
155
- export default ShareRecipientsInput
104
+ export default ShareAutosuggestWrapper
@@ -4,7 +4,7 @@ import React from 'react'
4
4
  import { createMockClient } from 'cozy-client'
5
5
  import flag from 'cozy-flags'
6
6
 
7
- import ShareRecipientsInput from './ShareRecipientsInput'
7
+ import ShareAutosuggestWrapper from './ShareAutosuggestWrapper'
8
8
  import AppLike from './SharingBanner/test/AppLike'
9
9
 
10
10
  jest.mock('cozy-flags', () => ({
@@ -37,7 +37,7 @@ jest.mock('./ShareAutosuggest', () => ({
37
37
  }
38
38
  }))
39
39
 
40
- describe('ShareRecipientsInput component', () => {
40
+ describe('ShareAutosuggestWrapper component', () => {
41
41
  const setup = (
42
42
  { contacts, contactGroups, unreachableContact },
43
43
  { currentRecipients, recipients } = {}
@@ -60,7 +60,7 @@ describe('ShareRecipientsInput component', () => {
60
60
  })
61
61
  render(
62
62
  <AppLike client={mockClient}>
63
- <ShareRecipientsInput
63
+ <ShareAutosuggestWrapper
64
64
  onPick={() => {}}
65
65
  onRemove={() => {}}
66
66
  placeholder="Enter recipients here"
@@ -4,7 +4,7 @@ import React from 'react'
4
4
  import flag from 'cozy-flags'
5
5
  import { useI18n } from 'twake-i18n'
6
6
 
7
- import ShareRecipientsInput from './ShareRecipientsInput'
7
+ import ShareAutosuggestWrapper from './ShareAutosuggestWrapper'
8
8
  import ShareTypeSelect from './Sharetypeselect'
9
9
  import {
10
10
  mergeRecipients,
@@ -60,7 +60,7 @@ export const ShareByEmail = ({
60
60
  return (
61
61
  <div className={styles['coz-form-group']}>
62
62
  <div className={styles['coz-form']}>
63
- <ShareRecipientsInput
63
+ <ShareAutosuggestWrapper
64
64
  placeholder={
65
65
  pendingRecipients.length === 0
66
66
  ? t(`${documentType}.share.shareByEmail.emailPlaceholder`)
@@ -3,7 +3,6 @@ import React from 'react'
3
3
  import snarkdown from 'snarkdown'
4
4
 
5
5
  import { Icon, SyncCozy, ToTheCloud } from '@linagora/twake-icons'
6
- import { useClient } from 'cozy-client'
7
6
  import flag from 'cozy-flags'
8
7
  import Alert from 'cozy-ui/transpiled/react/Alert'
9
8
  import Avatar from 'cozy-ui/transpiled/react/Avatar'
@@ -17,11 +16,9 @@ import styles from '../../../styles/publicBanner.styl'
17
16
  const getPublicNameFromSharing = sharing =>
18
17
  sharing.attributes.members[0].public_name
19
18
 
20
- const PublicBannerCozyToCozyContent = ({ sharing }) => {
19
+ const PublicBannerCozyToCozyContent = ({ sharing, avatarURL }) => {
21
20
  const { t } = useI18n()
22
- const client = useClient()
23
21
  const name = getPublicNameFromSharing(sharing)
24
- const avatarURL = `${client.options.uri}/public/avatar?fallback=initials`
25
22
 
26
23
  const withAvatar = snarkdown(
27
24
  t('Share.banner.shared_from', {
@@ -43,13 +40,15 @@ const PublicBannerCozyToCozyContent = ({ sharing }) => {
43
40
  )
44
41
  }
45
42
  PublicBannerCozyToCozyContent.propTypes = {
46
- sharing: PropTypes.object.isRequired
43
+ sharing: PropTypes.object.isRequired,
44
+ avatarURL: PropTypes.string.isRequired
47
45
  }
48
46
 
49
47
  const openExternalLink = url => (window.location = url)
50
48
 
51
49
  const SharingBannerCozyToCozy = ({
52
50
  sharing,
51
+ avatarURL,
53
52
  isSharingShortcutCreated,
54
53
  addSharingLink,
55
54
  onClose
@@ -90,7 +89,7 @@ const SharingBannerCozyToCozy = ({
90
89
  </>
91
90
  }
92
91
  >
93
- <PublicBannerCozyToCozyContent sharing={sharing} />
92
+ <PublicBannerCozyToCozyContent sharing={sharing} avatarURL={avatarURL} />
94
93
  </Alert>
95
94
  )
96
95
  }
@@ -162,6 +161,7 @@ SharingBannerByLink.propTypes = {
162
161
  }
163
162
  SharingBannerCozyToCozy.propTypes = {
164
163
  sharing: PropTypes.object.isRequired,
164
+ avatarURL: PropTypes.string.isRequired,
165
165
  isSharingShortcutCreated: PropTypes.bool.isRequired,
166
166
  addSharingLink: PropTypes.string.isRequired,
167
167
  onClose: PropTypes.func.isRequired
@@ -1,22 +1,14 @@
1
1
  import { render } from '@testing-library/react'
2
2
  import React from 'react'
3
3
 
4
- import { useClient } from 'cozy-client'
5
4
  import { BreakpointsProvider } from 'cozy-ui/transpiled/react/providers/Breakpoints'
6
5
  import { useI18n } from 'twake-i18n'
7
6
 
8
7
  import { SharingBannerCozyToCozy } from './PublicBanner'
9
8
 
10
9
  jest.mock('twake-i18n')
11
- jest.mock('cozy-client')
12
10
 
13
11
  describe('PublicBanner', () => {
14
- beforeEach(() => {
15
- useClient.mockReturnValue({
16
- options: { uri: 'http://cozy.localhost:8080' }
17
- })
18
- })
19
-
20
12
  it('should not add dangerous html', () => {
21
13
  // Given
22
14
  useI18n.mockReturnValue({
@@ -35,6 +27,7 @@ describe('PublicBanner', () => {
35
27
  <BreakpointsProvider>
36
28
  <SharingBannerCozyToCozy
37
29
  sharing={sharing}
30
+ avatarURL="http://cozy.localhost:8080/public/avatar?fallback=initials"
38
31
  isSharingShortcutCreated={true}
39
32
  addSharingLink="discoveryLink"
40
33
  onClose={() => {}}
@@ -3,7 +3,11 @@ import React, { useState, useCallback } from 'react'
3
3
 
4
4
  import { SharingBannerByLink, SharingBannerCozyToCozy } from './PublicBanner'
5
5
 
6
- export const SharingBanner = ({ sharingInfos, hideCreateCozyAction }) => {
6
+ export const SharingBanner = ({
7
+ sharingInfos,
8
+ avatarURL,
9
+ hideCreateCozyAction
10
+ }) => {
7
11
  const [isOpened, setIsOpened] = useState(true)
8
12
  const onClose = useCallback(() => setIsOpened(false), [setIsOpened])
9
13
 
@@ -22,6 +26,7 @@ export const SharingBanner = ({ sharingInfos, hideCreateCozyAction }) => {
22
26
  <SharingBannerCozyToCozy
23
27
  isSharingShortcutCreated={isSharingShortcutCreated}
24
28
  sharing={sharing}
29
+ avatarURL={avatarURL}
25
30
  addSharingLink={addSharingLink}
26
31
  onClose={onClose}
27
32
  />
@@ -31,6 +36,7 @@ export const SharingBanner = ({ sharingInfos, hideCreateCozyAction }) => {
31
36
 
32
37
  SharingBanner.propTypes = {
33
38
  sharingInfos: PropTypes.object,
39
+ avatarURL: PropTypes.string,
34
40
  hideCreateCozyAction: PropTypes.bool
35
41
  }
36
42
 
@@ -1,15 +1,19 @@
1
1
  import PropTypes from 'prop-types'
2
2
  import React from 'react'
3
3
 
4
+ import { useClient } from 'cozy-client'
5
+
4
6
  import { SharingBanner } from './components/SharingBanner'
5
7
  import { useSharingInfos } from './hooks/useSharingInfos'
6
8
  import withLocales from '../../hoc/withLocales'
7
9
 
8
10
  const Plugin = ({ previewPath, hideCreateCozyAction }) => {
11
+ const client = useClient()
9
12
  const sharingInfos = useSharingInfos(previewPath)
10
13
  return (
11
14
  <SharingBanner
12
15
  sharingInfos={sharingInfos}
16
+ avatarURL={`${client.options.uri}/public/avatar?fallback=initials`}
13
17
  hideCreateCozyAction={hideCreateCozyAction}
14
18
  />
15
19
  )
@@ -1,5 +1,10 @@
1
+ import { models } from 'cozy-client'
2
+ import flag from 'cozy-flags'
3
+
1
4
  import { validateEmail } from '../helpers/email'
2
5
 
6
+ const ContactModel = models.contact
7
+
3
8
  // Return the contact object if present
4
9
  // Else check if a contact exist with this email address, if yes return it
5
10
  // if not create it
@@ -59,3 +64,81 @@ export const getContactsFromGroupId = (contacts = [], groupId) => {
59
64
  export const isContactToBeCreated = contact => {
60
65
  return !contact._id
61
66
  }
67
+
68
+ const isCurrentUser = contact => contact.me === true
69
+
70
+ /**
71
+ * Computes the contacts and groups to suggest when adding recipients to a
72
+ * sharing. The unreachable contacts only count in the members of the groups,
73
+ * when flag sharing.show-recipient-groups is enabled.
74
+ *
75
+ * @param {object} params
76
+ * @param {object[]} params.reachableContacts - Contacts that can be shared with
77
+ * @param {object[]} params.unreachableContactsWithGroups - Contacts that cannot be shared with but belong to a group
78
+ * @param {object[]} params.contactGroups - Groups of contacts
79
+ * @param {object[]} params.currentRecipients - Recipients already in the sharing
80
+ * @param {object[]} params.pendingRecipients - Recipients picked but not yet shared with
81
+ * @returns {object[]} The contacts, then the groups with their members
82
+ */
83
+ export const computeContactsAndGroups = ({
84
+ reachableContacts,
85
+ unreachableContactsWithGroups,
86
+ contactGroups,
87
+ currentRecipients,
88
+ pendingRecipients
89
+ }) => {
90
+ const suggestedContacts = reachableContacts.filter(
91
+ contact =>
92
+ !isCurrentUser(contact) &&
93
+ // We do not want contact already in the sharing
94
+ !currentRecipients.find(
95
+ r => r.email === ContactModel.getPrimaryEmail(contact)
96
+ ) &&
97
+ // We do not want contact currently in pending contacts (in chips)
98
+ !pendingRecipients.find(r => r._id === contact._id)
99
+ )
100
+
101
+ const unreachableContacts = unreachableContactsWithGroups.filter(
102
+ contact => !isCurrentUser(contact)
103
+ )
104
+ const currentRecipientGroups = currentRecipients
105
+ .map(({ id }) => id)
106
+ .filter(Boolean)
107
+ const suggestedGroups = contactGroups
108
+ .filter(
109
+ ({ _id }) =>
110
+ !flag('sharing.show-recipient-groups') ||
111
+ !currentRecipientGroups.includes(_id)
112
+ )
113
+ .map(contactGroup => {
114
+ const reachableMembers = getContactsFromGroupId(
115
+ suggestedContacts,
116
+ contactGroup.id
117
+ ).map(contact => ({
118
+ ...contact,
119
+ isReachable: true
120
+ }))
121
+
122
+ if (flag('sharing.show-recipient-groups') !== true) {
123
+ return {
124
+ ...contactGroup,
125
+ members: reachableMembers
126
+ }
127
+ }
128
+
129
+ const unreachableMembers = getContactsFromGroupId(
130
+ unreachableContacts,
131
+ contactGroup.id
132
+ ).map(contact => ({
133
+ ...contact,
134
+ isReachable: false
135
+ }))
136
+
137
+ return {
138
+ ...contactGroup,
139
+ members: [...reachableMembers, ...unreachableMembers]
140
+ }
141
+ })
142
+
143
+ return [...suggestedContacts, ...suggestedGroups]
144
+ }
@@ -1,4 +1,11 @@
1
- import { getOrCreateFromArray } from './contacts'
1
+ import flag from 'cozy-flags'
2
+
3
+ import { computeContactsAndGroups, getOrCreateFromArray } from './contacts'
4
+
5
+ jest.mock('cozy-flags', () => ({
6
+ __esModule: true,
7
+ default: jest.fn(() => null)
8
+ }))
2
9
 
3
10
  const findMock = jest
4
11
  .fn()
@@ -51,3 +58,73 @@ describe('getOrCreateFromArray', () => {
51
58
  ])
52
59
  })
53
60
  })
61
+
62
+ describe('computeContactsAndGroups', () => {
63
+ const group = { id: 'friends', _id: 'friends', name: 'Friends' }
64
+ const inGroup = { relationships: { groups: { data: [{ _id: 'friends' }] } } }
65
+ const alice = {
66
+ _id: 'alice',
67
+ email: [{ address: 'alice@cozy.cc', primary: true }],
68
+ ...inGroup
69
+ }
70
+ const bob = { _id: 'bob', email: [{ address: 'bob@cozy.cc' }], ...inGroup }
71
+ const me = { _id: 'me', me: true, ...inGroup }
72
+ const carol = { _id: 'carol', ...inGroup }
73
+
74
+ const setup = ({
75
+ currentRecipients = [],
76
+ pendingRecipients = [],
77
+ showRecipientGroups = null
78
+ } = {}) => {
79
+ flag.mockReturnValue(showRecipientGroups)
80
+ return computeContactsAndGroups({
81
+ reachableContacts: [alice, bob, me],
82
+ unreachableContactsWithGroups: [carol, me],
83
+ contactGroups: [group],
84
+ currentRecipients,
85
+ pendingRecipients
86
+ })
87
+ }
88
+
89
+ it('suggests the contacts, then the groups with their reachable members', () => {
90
+ const [first, second, friends] = setup()
91
+
92
+ expect([first._id, second._id, friends._id]).toEqual([
93
+ 'alice',
94
+ 'bob',
95
+ 'friends'
96
+ ])
97
+ expect(friends.members.map(m => [m._id, m.isReachable])).toEqual([
98
+ ['alice', true],
99
+ ['bob', true]
100
+ ])
101
+ })
102
+
103
+ it('does not suggest the recipients already shared with or picked', () => {
104
+ const contactsAndGroups = setup({
105
+ currentRecipients: [{ email: 'alice@cozy.cc' }],
106
+ pendingRecipients: [bob]
107
+ })
108
+
109
+ expect(contactsAndGroups.map(c => c._id)).toEqual(['friends'])
110
+ })
111
+
112
+ it('adds the unreachable members when flag sharing.show-recipient-groups is enabled', () => {
113
+ const [, , friends] = setup({ showRecipientGroups: true })
114
+
115
+ expect(friends.members.map(m => [m._id, m.isReachable])).toEqual([
116
+ ['alice', true],
117
+ ['bob', true],
118
+ ['carol', false]
119
+ ])
120
+ })
121
+
122
+ it('does not suggest the groups already in the sharing when flag sharing.show-recipient-groups is enabled', () => {
123
+ const contactsAndGroups = setup({
124
+ currentRecipients: [{ id: 'friends' }],
125
+ showRecipientGroups: true
126
+ })
127
+
128
+ expect(contactsAndGroups.map(c => c._id)).toEqual(['alice', 'bob'])
129
+ })
130
+ })