cozy-sharing 40.1.9 → 40.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,16 @@
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.2.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@40.1.9...cozy-sharing@40.2.0) (2026-10-28)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **cozy-sharing:** Ignore the email case when filtering suggestions ([4057cf0](https://github.com/cozy/cozy-libs/commit/4057cf010f54c646d8518aa787e03be445b60df2))
11
+
12
+ ### Features
13
+
14
+ - **cozy-sharing:** Suggest recipients from the stack ([8b06972](https://github.com/cozy/cozy-libs/commit/8b0697256b118c365ca9f7315aae3f00eef6ebd6)), closes [linagora/twake-workplace-private#1929](https://github.com/linagora/twake-workplace-private/issues/1929)
15
+
6
16
  ## [40.1.9](https://github.com/cozy/cozy-libs/compare/cozy-sharing@40.1.8...cozy-sharing@40.1.9) (2026-10-28)
7
17
 
8
18
  ### Bug Fixes
package/README.md CHANGED
@@ -40,6 +40,7 @@ import SharingProvider from 'cozy-sharing'
40
40
  | `onShared` | no | Callback fired after a share is created |
41
41
  | `isPublic` | no | Set to `true` when rendering a public page (disables fetching) |
42
42
  | `previewPath` | no | Custom preview path sent to the share creation |
43
+ | `contactsSuggestEnabled` | no | Suggest the recipients with `GET /contacts/suggest` instead of loading every contact |
43
44
 
44
45
  ### Built-in components
45
46
 
@@ -1451,7 +1451,9 @@ export var SharingProvider = /*#__PURE__*/function (_Component) {
1451
1451
  // but if we don't, consumers don't rerender when the state changes after loading the sharings,
1452
1452
  // probably because the state object remains the same...
1453
1453
  return /*#__PURE__*/React.createElement(SharingContext.Provider, {
1454
- value: _objectSpread({}, this.state)
1454
+ value: _objectSpread(_objectSpread({}, this.state), {}, {
1455
+ contactsSuggestEnabled: this.props.contactsSuggestEnabled
1456
+ })
1455
1457
  }, this.props.children);
1456
1458
  }
1457
1459
  }]);
@@ -208,7 +208,9 @@ describe('PermissionTypeMenu component', function () {
208
208
  }]
209
209
  };
210
210
  beforeEach(function () {
211
- flag.mockReturnValue(true);
211
+ flag.mockImplementation(function (name) {
212
+ return name === 'drive.federated-shared-folder.enabled';
213
+ });
212
214
  mockHasSharedParent.mockReturnValue(true);
213
215
  mockGetSharingById.mockReturnValue({
214
216
  id: 'sharing-123',
@@ -1,7 +1,10 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
+ import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
3
+ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
4
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
5
+ import _regeneratorRuntime from "@babel/runtime/regenerator";
3
6
  import PropTypes from 'prop-types';
4
- import React, { useState, useRef } from 'react';
7
+ import React, { useEffect, useState, useRef } from 'react';
5
8
  import Autosuggest from 'react-autosuggest';
6
9
  import Chip from 'cozy-ui/transpiled/react/Chips';
7
10
  import { Spinner } from 'cozy-ui/transpiled/react/Spinner';
@@ -10,6 +13,7 @@ import { MemberAvatar } from './Avatar/MemberAvatar';
10
13
  import ContactSuggestion from './ContactSuggestion';
11
14
  import { isContactToBeCreated } from '../helpers/contacts';
12
15
  import { extractEmails, validateEmail } from '../helpers/email';
16
+ import logger from '../logger';
13
17
  import { getDisplayName, DOCTYPE_CONTACTS, DOCTYPE_GROUPS } from '../models';
14
18
  var styles = {
15
19
  "container": "autosuggest__container___1Yro7",
@@ -24,6 +28,8 @@ var styles = {
24
28
  "endAdornment": "autosuggest__endAdornment___2EwTN"
25
29
  };
26
30
  import { contactOrGroupMatch } from '../suggestionMatchers';
31
+ var FETCH_CONTACTS_DEBOUNCE = 300;
32
+ var FETCH_CONTACTS_MIN_LENGTH = 3;
27
33
 
28
34
  var ShareAutosuggest = function ShareAutosuggest(_ref) {
29
35
  var loading = _ref.loading,
@@ -35,7 +41,8 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
35
41
  enableCreateContact = _ref.enableCreateContact,
36
42
  placeholder = _ref.placeholder,
37
43
  autoFocus = _ref.autoFocus,
38
- endAdornment = _ref.endAdornment;
44
+ endAdornment = _ref.endAdornment,
45
+ fetchContacts = _ref.fetchContacts;
39
46
 
40
47
  var _useState = useState(''),
41
48
  _useState2 = _slicedToArray(_useState, 2),
@@ -57,8 +64,20 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
57
64
  isLoadingDisplayed = _useState8[0],
58
65
  setLoadingDisplayed = _useState8[1];
59
66
 
67
+ var _useState9 = useState(false),
68
+ _useState10 = _slicedToArray(_useState9, 2),
69
+ isFetching = _useState10[0],
70
+ setIsFetching = _useState10[1];
71
+
60
72
  var autosuggestRef = useRef(null);
61
73
  var containerRef = useRef(null);
74
+ var fetchTimeoutRef = useRef(null);
75
+ var fetchedValueRef = useRef(null);
76
+ useEffect(function () {
77
+ return function () {
78
+ return clearTimeout(fetchTimeoutRef.current);
79
+ };
80
+ }, []);
62
81
 
63
82
  var computeSuggestions = function computeSuggestions(value) {
64
83
  var inputValue = value.trim().toLowerCase();
@@ -67,10 +86,7 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
67
86
  });
68
87
  };
69
88
 
70
- var onSuggestionsFetchRequested = function onSuggestionsFetchRequested(_ref2) {
71
- var value = _ref2.value;
72
- var computedSuggestions = computeSuggestions(value);
73
-
89
+ var showSuggestions = function showSuggestions(computedSuggestions, value) {
74
90
  if (computedSuggestions.length > 0) {
75
91
  setSuggestions(computedSuggestions);
76
92
  } else if (enableCreateContact && validateEmail(value)) {
@@ -83,7 +99,63 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
83
99
  }
84
100
  };
85
101
 
102
+ var onSuggestionsFetchRequested = function onSuggestionsFetchRequested(_ref2) {
103
+ var value = _ref2.value;
104
+ var computedSuggestions = computeSuggestions(value);
105
+ clearTimeout(fetchTimeoutRef.current);
106
+ fetchedValueRef.current = value;
107
+
108
+ if (!fetchContacts || value.trim().length < FETCH_CONTACTS_MIN_LENGTH) {
109
+ setIsFetching(false);
110
+ showSuggestions(computedSuggestions, value);
111
+ return;
112
+ }
113
+
114
+ setIsFetching(true);
115
+ fetchTimeoutRef.current = setTimeout( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
116
+ var contacts;
117
+ return _regeneratorRuntime.wrap(function _callee$(_context) {
118
+ while (1) switch (_context.prev = _context.next) {
119
+ case 0:
120
+ contacts = [];
121
+ _context.prev = 1;
122
+ _context.next = 4;
123
+ return fetchContacts(value.trim());
124
+
125
+ case 4:
126
+ contacts = _context.sent;
127
+ _context.next = 10;
128
+ break;
129
+
130
+ case 7:
131
+ _context.prev = 7;
132
+ _context.t0 = _context["catch"](1);
133
+ logger.error('Failed to fetch contact suggestions', _context.t0);
134
+
135
+ case 10:
136
+ if (!(fetchedValueRef.current !== value)) {
137
+ _context.next = 12;
138
+ break;
139
+ }
140
+
141
+ return _context.abrupt("return");
142
+
143
+ case 12:
144
+ setIsFetching(false);
145
+ showSuggestions([].concat(_toConsumableArray(contacts), _toConsumableArray(computedSuggestions)), value);
146
+
147
+ case 14:
148
+ case "end":
149
+ return _context.stop();
150
+ }
151
+ }, _callee, null, [[1, 7]]);
152
+ })), FETCH_CONTACTS_DEBOUNCE);
153
+ };
154
+
86
155
  var onSuggestionsClearRequested = function onSuggestionsClearRequested() {
156
+ clearTimeout(fetchTimeoutRef.current);
157
+ fetchedValueRef.current = null;
158
+ setIsFetching(false);
87
159
  setSuggestions([]);
88
160
  };
89
161
 
@@ -91,9 +163,9 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
91
163
  setIsPasted(true);
92
164
  };
93
165
 
94
- var onAutosuggestChange = function onAutosuggestChange(event, _ref3) {
95
- var newValue = _ref3.newValue,
96
- method = _ref3.method;
166
+ var onAutosuggestChange = function onAutosuggestChange(event, _ref4) {
167
+ var newValue = _ref4.newValue,
168
+ method = _ref4.method;
97
169
 
98
170
  if (isPasted) {
99
171
  var emails = extractEmails(newValue);
@@ -126,9 +198,9 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
126
198
  }
127
199
  };
128
200
 
129
- var onKeyUp = function onKeyUp(_ref4) {
130
- var key = _ref4.key,
131
- keyCode = _ref4.keyCode;
201
+ var onKeyUp = function onKeyUp(_ref5) {
202
+ var key = _ref5.key,
203
+ keyCode = _ref5.keyCode;
132
204
 
133
205
  if (recipients.length > 0 && inputValue === '' && (key === 'Backspace' || keyCode === 8 || key === 'Delete' || keyCode === 46)) {
134
206
  document.getElementById("recipient_".concat(recipients.length - 1)).focus();
@@ -202,7 +274,7 @@ var ShareAutosuggest = function ShareAutosuggest(_ref) {
202
274
  }), /*#__PURE__*/React.createElement("input", _extends({}, inputProps, {
203
275
  onKeyPress: onKeyPress,
204
276
  onKeyUp: onKeyUp
205
- })), loading && isLoadingDisplayed ? /*#__PURE__*/React.createElement(Spinner, {
277
+ })), (loading || isFetching) && isLoadingDisplayed ? /*#__PURE__*/React.createElement(Spinner, {
206
278
  color: "var(--primaryColor)"
207
279
  }) : null, endAdornment ? /*#__PURE__*/React.createElement("div", {
208
280
  className: styles['endAdornment']
@@ -258,6 +330,7 @@ ShareAutosuggest.propTypes = {
258
330
  enableCreateContact: PropTypes.bool,
259
331
  placeholder: PropTypes.string,
260
332
  autoFocus: PropTypes.bool,
261
- endAdornment: PropTypes.node
333
+ endAdornment: PropTypes.node,
334
+ fetchContacts: PropTypes.func
262
335
  };
263
336
  export default ShareAutosuggest;
@@ -1,4 +1,6 @@
1
- import { render, fireEvent, screen } from '@testing-library/react';
1
+ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
+ import _regeneratorRuntime from "@babel/runtime/regenerator";
3
+ import { act, render, fireEvent, screen } from '@testing-library/react';
2
4
  import React from 'react';
3
5
  import ShareAutosuggest from './ShareAutosuggest';
4
6
  import AppLike from '../../test/AppLike';
@@ -16,7 +18,8 @@ describe('ShareAutosuggest', function () {
16
18
  _ref$loading = _ref.loading,
17
19
  loading = _ref$loading === void 0 ? false : _ref$loading,
18
20
  _ref$enableCreateCont = _ref.enableCreateContact,
19
- enableCreateContact = _ref$enableCreateCont === void 0 ? true : _ref$enableCreateCont;
21
+ enableCreateContact = _ref$enableCreateCont === void 0 ? true : _ref$enableCreateCont,
22
+ fetchContacts = _ref.fetchContacts;
20
23
  render( /*#__PURE__*/React.createElement(AppLike, null, /*#__PURE__*/React.createElement(ShareAutosuggest, {
21
24
  loading: loading,
22
25
  contactsAndGroups: [],
@@ -24,7 +27,8 @@ describe('ShareAutosuggest', function () {
24
27
  onPick: onPick,
25
28
  recipients: [],
26
29
  onRemove: onRemove,
27
- enableCreateContact: enableCreateContact
30
+ enableCreateContact: enableCreateContact,
31
+ fetchContacts: fetchContacts
28
32
  })));
29
33
  };
30
34
 
@@ -123,4 +127,120 @@ describe('ShareAutosuggest', function () {
123
127
  });
124
128
  expect(screen.queryByText('loading')).toBeNull();
125
129
  });
130
+ describe('with fetchContacts', function () {
131
+ beforeEach(function () {
132
+ jest.useFakeTimers(); // Not implemented by jsdom, called when a suggestion is highlighted
133
+
134
+ Element.prototype.scrollIntoView = jest.fn();
135
+ });
136
+ afterEach(function () {
137
+ jest.useRealTimers();
138
+ });
139
+ it('should suggest the fetched contacts, debounced and from 3 characters', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
140
+ var fetchContacts, inputNode;
141
+ return _regeneratorRuntime.wrap(function _callee$(_context) {
142
+ while (1) switch (_context.prev = _context.next) {
143
+ case 0:
144
+ fetchContacts = jest.fn().mockResolvedValue([{
145
+ _id: 'org-member-1',
146
+ _type: 'io.cozy.contacts',
147
+ displayName: 'Jean Dupont',
148
+ email: [{
149
+ address: 'jean@org.example',
150
+ primary: true
151
+ }]
152
+ }]);
153
+ setup({
154
+ onPick: jest.fn(),
155
+ onRemove: jest.fn(),
156
+ fetchContacts: fetchContacts
157
+ });
158
+ inputNode = screen.getByPlaceholderText('myPlaceHolder');
159
+ fireEvent.change(inputNode, {
160
+ target: {
161
+ value: 'du'
162
+ }
163
+ });
164
+ act(function () {
165
+ return jest.runAllTimers();
166
+ });
167
+ expect(fetchContacts).not.toHaveBeenCalled();
168
+ fireEvent.change(inputNode, {
169
+ target: {
170
+ value: 'dup'
171
+ }
172
+ });
173
+ fireEvent.change(inputNode, {
174
+ target: {
175
+ value: 'dupo'
176
+ }
177
+ });
178
+ act(function () {
179
+ return jest.runAllTimers();
180
+ });
181
+ expect(fetchContacts).toHaveBeenCalledTimes(1);
182
+ expect(fetchContacts).toHaveBeenCalledWith('dupo');
183
+ _context.t0 = expect;
184
+ _context.next = 14;
185
+ return screen.findByText('Jean Dupont');
186
+
187
+ case 14:
188
+ _context.t1 = _context.sent;
189
+ (0, _context.t0)(_context.t1).toBeInTheDocument();
190
+
191
+ case 16:
192
+ case "end":
193
+ return _context.stop();
194
+ }
195
+ }, _callee);
196
+ })));
197
+ it('should show the loading indicator until the contacts are fetched', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
198
+ var resolveFetch, fetchContacts, inputNode;
199
+ return _regeneratorRuntime.wrap(function _callee3$(_context3) {
200
+ while (1) switch (_context3.prev = _context3.next) {
201
+ case 0:
202
+ fetchContacts = jest.fn(function () {
203
+ return new Promise(function (resolve) {
204
+ return resolveFetch = resolve;
205
+ });
206
+ });
207
+ setup({
208
+ onPick: jest.fn(),
209
+ onRemove: jest.fn(),
210
+ fetchContacts: fetchContacts
211
+ });
212
+ inputNode = screen.getByPlaceholderText('myPlaceHolder');
213
+ fireEvent.change(inputNode, {
214
+ target: {
215
+ value: 'dup'
216
+ }
217
+ });
218
+ expect(screen.getByText('loading')).toBeInTheDocument();
219
+ act(function () {
220
+ return jest.runAllTimers();
221
+ });
222
+ _context3.next = 8;
223
+ return act( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
224
+ return _regeneratorRuntime.wrap(function _callee2$(_context2) {
225
+ while (1) switch (_context2.prev = _context2.next) {
226
+ case 0:
227
+ return _context2.abrupt("return", resolveFetch([]));
228
+
229
+ case 1:
230
+ case "end":
231
+ return _context2.stop();
232
+ }
233
+ }, _callee2);
234
+ })));
235
+
236
+ case 8:
237
+ expect(screen.queryByText('loading')).toBeNull();
238
+
239
+ case 9:
240
+ case "end":
241
+ return _context3.stop();
242
+ }
243
+ }, _callee3);
244
+ })));
245
+ });
126
246
  });
@@ -1,4 +1,6 @@
1
+ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
1
2
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
3
+ import _regeneratorRuntime from "@babel/runtime/regenerator";
2
4
 
3
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; }
4
6
 
@@ -6,18 +8,25 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
6
8
 
7
9
  import PropTypes from 'prop-types';
8
10
  import React from 'react';
9
- import { useQueryAll, isQueryLoading } from 'cozy-client';
11
+ import { useClient, useQueryAll, isQueryLoading } from 'cozy-client';
12
+ import { models } from 'cozy-client';
10
13
  import flag from 'cozy-flags';
11
14
  import ShareAutosuggest from './ShareAutosuggest';
12
- import { computeContactsAndGroups } from '../helpers/contacts';
15
+ import { computeContactsAndGroups, suggestContacts } from '../helpers/contacts';
16
+ import { useSharingContext } from '../hooks/useSharingContext';
17
+ import { DOCTYPE_GROUPS } from '../models';
13
18
  import { buildReachableContactsQuery, buildContactGroupsQuery, buildUnreachableContactsWithGroupsQuery } from '../queries/queries';
19
+ var ContactModel = models.contact;
14
20
  /**
15
21
  * ShareAutosuggestWrapper is responsible for fetching contacts and groups.
16
22
  * We retrieve all the contacts that are reachable and the groups they belong to.
17
23
  * In order to display the total number of members in each group, we also retrieve the contacts that are not reachable.
24
+ * With contactsSuggestEnabled, the stack suggests the contacts and we only retrieve the group members.
18
25
  */
19
26
 
20
27
  var ShareAutosuggestWrapper = function ShareAutosuggestWrapper(_ref) {
28
+ var _useSharingContext;
29
+
21
30
  var currentRecipients = _ref.currentRecipients,
22
31
  recipients = _ref.recipients,
23
32
  placeholder = _ref.placeholder,
@@ -27,7 +36,11 @@ var ShareAutosuggestWrapper = function ShareAutosuggestWrapper(_ref) {
27
36
  disabled = _ref.disabled,
28
37
  autoFocus = _ref.autoFocus,
29
38
  endAdornment = _ref.endAdornment;
30
- var reachableContactsQuery = buildReachableContactsQuery();
39
+ var client = useClient();
40
+ var contactsSuggestEnabled = (_useSharingContext = useSharingContext()) === null || _useSharingContext === void 0 ? void 0 : _useSharingContext.contactsSuggestEnabled;
41
+ var reachableContactsQuery = buildReachableContactsQuery({
42
+ inGroups: contactsSuggestEnabled
43
+ });
31
44
  var reachableContactsResult = useQueryAll(reachableContactsQuery.definition, reachableContactsQuery.options);
32
45
  var unreachableContactsWithGroupsQuery = buildUnreachableContactsWithGroupsQuery();
33
46
  var unreachableContactsWithGroupsResult = useQueryAll(unreachableContactsWithGroupsQuery.definition, _objectSpread(_objectSpread({}, unreachableContactsWithGroupsQuery.options), {}, {
@@ -36,13 +49,55 @@ var ShareAutosuggestWrapper = function ShareAutosuggestWrapper(_ref) {
36
49
  var contactGroupsQuery = buildContactGroupsQuery();
37
50
  var contactGroupsResult = useQueryAll(contactGroupsQuery.definition, contactGroupsQuery.options);
38
51
  var isLoading = isQueryLoading(reachableContactsResult) || reachableContactsResult.hasMore || isQueryLoading(contactGroupsResult) || contactGroupsResult.hasMore || flag('sharing.show-recipient-groups') && (isQueryLoading(unreachableContactsWithGroupsResult) || unreachableContactsWithGroupsResult.hasMore);
39
- var contactsAndGroups = computeContactsAndGroups({
52
+ var suggestedContactsAndGroups = computeContactsAndGroups({
40
53
  reachableContacts: reachableContactsResult.data || [],
41
54
  unreachableContactsWithGroups: unreachableContactsWithGroupsResult.data || [],
42
55
  contactGroups: contactGroupsResult.data || [],
43
56
  currentRecipients: currentRecipients,
44
57
  pendingRecipients: recipients
45
58
  });
59
+ var contactsAndGroups = contactsSuggestEnabled ? suggestedContactsAndGroups.filter(function (_ref2) {
60
+ var _type = _ref2._type;
61
+ return _type === DOCTYPE_GROUPS;
62
+ }) : suggestedContactsAndGroups; // The stack matches the emails whatever their case
63
+
64
+ var normalizeEmail = function normalizeEmail(email) {
65
+ return email === null || email === void 0 ? void 0 : email.trim().toLowerCase();
66
+ };
67
+
68
+ var isRecipient = function isRecipient(email) {
69
+ return currentRecipients.some(function (r) {
70
+ return normalizeEmail(r.email) === email;
71
+ }) || recipients.some(function (r) {
72
+ return normalizeEmail(ContactModel.getPrimaryEmail(r)) === email;
73
+ });
74
+ };
75
+
76
+ var fetchContacts = /*#__PURE__*/function () {
77
+ var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(q) {
78
+ return _regeneratorRuntime.wrap(function _callee$(_context) {
79
+ while (1) switch (_context.prev = _context.next) {
80
+ case 0:
81
+ _context.next = 2;
82
+ return suggestContacts(client, q);
83
+
84
+ case 2:
85
+ return _context.abrupt("return", _context.sent.filter(function (contact) {
86
+ return !isRecipient(normalizeEmail(ContactModel.getPrimaryEmail(contact)));
87
+ }));
88
+
89
+ case 3:
90
+ case "end":
91
+ return _context.stop();
92
+ }
93
+ }, _callee);
94
+ }));
95
+
96
+ return function fetchContacts(_x) {
97
+ return _ref3.apply(this, arguments);
98
+ };
99
+ }();
100
+
46
101
  return /*#__PURE__*/React.createElement(ShareAutosuggest, {
47
102
  loading: isLoading,
48
103
  disabled: disabled,
@@ -53,7 +108,8 @@ var ShareAutosuggestWrapper = function ShareAutosuggestWrapper(_ref) {
53
108
  enableCreateContact: enableCreateContact,
54
109
  placeholder: placeholder,
55
110
  autoFocus: autoFocus,
56
- endAdornment: endAdornment
111
+ endAdornment: endAdornment,
112
+ fetchContacts: contactsSuggestEnabled ? fetchContacts : undefined
57
113
  });
58
114
  };
59
115
 
@@ -14,11 +14,13 @@ jest.mock('cozy-flags', function () {
14
14
  })
15
15
  };
16
16
  });
17
+ var mockAutosuggestProps;
17
18
  jest.mock('./ShareAutosuggest', function () {
18
19
  return {
19
20
  __esModule: true,
20
- default: function _default(_ref) {
21
- var contactsAndGroups = _ref.contactsAndGroups;
21
+ default: function _default(props) {
22
+ mockAutosuggestProps = props;
23
+ var contactsAndGroups = props.contactsAndGroups;
22
24
  return /*#__PURE__*/React.createElement("ul", null, contactsAndGroups.map(function (contactOrGroup) {
23
25
  if (contactOrGroup._type === 'io.cozy.contacts') {
24
26
  return /*#__PURE__*/React.createElement("li", {
@@ -34,14 +36,16 @@ jest.mock('./ShareAutosuggest', function () {
34
36
  };
35
37
  });
36
38
  describe('ShareAutosuggestWrapper component', function () {
37
- var setup = function setup(_ref2) {
38
- var contacts = _ref2.contacts,
39
- contactGroups = _ref2.contactGroups,
40
- unreachableContact = _ref2.unreachableContact;
39
+ var setup = function setup(_ref) {
40
+ var contacts = _ref.contacts,
41
+ contactGroups = _ref.contactGroups,
42
+ unreachableContact = _ref.unreachableContact,
43
+ groupMembers = _ref.groupMembers;
41
44
 
42
- var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
43
- currentRecipients = _ref3.currentRecipients,
44
- recipients = _ref3.recipients;
45
+ var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
46
+ currentRecipients = _ref2.currentRecipients,
47
+ recipients = _ref2.recipients,
48
+ contactsSuggestEnabled = _ref2.contactsSuggestEnabled;
45
49
 
46
50
  var mockClient = createMockClient({
47
51
  queries: {
@@ -56,11 +60,18 @@ describe('ShareAutosuggestWrapper component', function () {
56
60
  'io.cozy.contacts/unreachable-with-groups': {
57
61
  doctype: 'io.cozy.contacts',
58
62
  data: unreachableContact
63
+ },
64
+ 'io.cozy.contacts/reachable-with-groups': {
65
+ doctype: 'io.cozy.contacts',
66
+ data: groupMembers
59
67
  }
60
68
  }
61
69
  });
62
70
  render( /*#__PURE__*/React.createElement(AppLike, {
63
- client: mockClient
71
+ client: mockClient,
72
+ sharingContextValue: {
73
+ contactsSuggestEnabled: contactsSuggestEnabled
74
+ }
64
75
  }, /*#__PURE__*/React.createElement(ShareAutosuggestWrapper, {
65
76
  onPick: function onPick() {},
66
77
  onRemove: function onRemove() {},
@@ -68,6 +79,7 @@ describe('ShareAutosuggestWrapper component', function () {
68
79
  currentRecipients: currentRecipients,
69
80
  recipients: recipients
70
81
  })));
82
+ return mockClient;
71
83
  };
72
84
 
73
85
  it('should include groups and contacts when are loaded', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
@@ -240,7 +252,9 @@ describe('ShareAutosuggestWrapper component', function () {
240
252
  return _regeneratorRuntime.wrap(function _callee4$(_context4) {
241
253
  while (1) switch (_context4.prev = _context4.next) {
242
254
  case 0:
243
- flag.mockReturnValue(true);
255
+ flag.mockImplementation(function (name) {
256
+ return name === 'sharing.show-recipient-groups';
257
+ });
244
258
  contacts = [{
245
259
  id: 'df563cc4-6440',
246
260
  _id: 'df563cc4-6440',
@@ -307,4 +321,92 @@ describe('ShareAutosuggestWrapper component', function () {
307
321
  }
308
322
  }, _callee4);
309
323
  })));
324
+ describe('with contactsSuggestEnabled', function () {
325
+ beforeEach(function () {
326
+ flag.mockReset();
327
+ });
328
+ it('should suggest only groups, with the count of their members', function () {
329
+ var groupMembers = [{
330
+ id: '5a3b4ccf-c257',
331
+ _id: '5a3b4ccf-c257',
332
+ _type: 'io.cozy.contacts',
333
+ name: {
334
+ givenName: 'Teagan',
335
+ familyName: 'Wolf'
336
+ },
337
+ relationships: {
338
+ groups: {
339
+ data: [{
340
+ _id: 'fe86af20-c6c5',
341
+ _type: 'io.cozy.contacts.groups'
342
+ }]
343
+ }
344
+ }
345
+ }];
346
+ var contactGroups = [{
347
+ id: 'fe86af20-c6c5',
348
+ _id: 'fe86af20-c6c5',
349
+ _type: 'io.cozy.contacts.groups',
350
+ name: 'Friends'
351
+ }];
352
+ setup({
353
+ contactGroups: contactGroups,
354
+ groupMembers: groupMembers
355
+ }, {
356
+ contactsSuggestEnabled: true
357
+ });
358
+ expect(screen.getByText('Friends - 1 members')).toBeInTheDocument();
359
+ expect(screen.queryByText('Teagan Wolf')).not.toBeInTheDocument();
360
+ });
361
+ it('should fetch suggestions without the current and pending recipients, whatever the case of their email', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5() {
362
+ var suggestion, mockClient, fetchJSON, contacts;
363
+ return _regeneratorRuntime.wrap(function _callee5$(_context5) {
364
+ while (1) switch (_context5.prev = _context5.next) {
365
+ case 0:
366
+ suggestion = function suggestion(email) {
367
+ return {
368
+ type: 'io.cozy.contacts',
369
+ attributes: {
370
+ fullname: email,
371
+ email: [{
372
+ address: email
373
+ }]
374
+ }
375
+ };
376
+ };
377
+
378
+ mockClient = setup({
379
+ contactGroups: [],
380
+ groupMembers: []
381
+ }, {
382
+ currentRecipients: [{
383
+ email: 'Current@Cozy.cc'
384
+ }],
385
+ recipients: [{
386
+ email: 'pending@cozy.cc'
387
+ }],
388
+ contactsSuggestEnabled: true
389
+ });
390
+ fetchJSON = jest.fn().mockResolvedValue({
391
+ data: [suggestion('current@cozy.cc'), suggestion('PENDING@cozy.cc'), suggestion('new@cozy.cc')]
392
+ });
393
+ jest.spyOn(mockClient, 'getStackClient').mockReturnValue({
394
+ fetchJSON: fetchJSON
395
+ });
396
+ _context5.next = 6;
397
+ return mockAutosuggestProps.fetchContacts('coz');
398
+
399
+ case 6:
400
+ contacts = _context5.sent;
401
+ expect(contacts.map(function (c) {
402
+ return c.fullname;
403
+ })).toEqual(['new@cozy.cc']);
404
+
405
+ case 8:
406
+ case "end":
407
+ return _context5.stop();
408
+ }
409
+ }, _callee5);
410
+ })));
411
+ });
310
412
  });