@ukhomeoffice/cop-react-form-renderer 6.14.3-alpha → 6.14.3

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.
@@ -7,63 +7,80 @@ exports.default = void 0;
7
7
  var _copReactComponents = require("@ukhomeoffice/cop-react-components");
8
8
  var _getOptions = _interopRequireDefault(require("./getOptions"));
9
9
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
- // Global Imports
11
-
12
- // Local imports
10
+ const MAX_NUMBER_RESULTS = 6000; // lower is better for performance, this still includes all ports
13
11
 
12
+ const assignFullMatchScore = (subStringMatchIndex, label) => {
13
+ const matchAtBeginning = subStringMatchIndex === 0;
14
+ let score = 0;
15
+ if (matchAtBeginning) {
16
+ score = 1;
17
+ }
18
+ if (!matchAtBeginning) {
19
+ const isCompleteWord = label.substring(subStringMatchIndex - 1).startsWith(" ");
20
+ score = isCompleteWord ? 2 : 3;
21
+ }
22
+ return score;
23
+ };
24
+ const noMatch = score => score === 0;
25
+ const lowerCaseMatch = (searchTerm, query) => typeof searchTerm !== 'string' ? false : searchTerm.toLowerCase().includes(query);
26
+ const assignSynonymsScore = (option, searchQuery) => {
27
+ let score = 0;
28
+ const isMatch = option.synonyms.some(synonym => lowerCaseMatch(synonym, searchQuery));
29
+ if (isMatch) {
30
+ score = 4;
31
+ }
32
+ return score;
33
+ };
34
+ const assignExtraFieldsScore = (config, option, searchQuery) => {
35
+ let score = 0;
36
+ const isMatch = config.item.extraFieldsToSearch.some(field => {
37
+ const fieldToSearch = option[field];
38
+ return lowerCaseMatch(fieldToSearch, searchQuery);
39
+ });
40
+ if (isMatch) {
41
+ score = 5;
42
+ }
43
+ return score;
44
+ };
45
+ const mapToScoreAndLabel = (option, searchQuery, labelMapper, config) => {
46
+ var _config$item;
47
+ const label = labelMapper ? labelMapper(option).toLowerCase() : option.label.toLowerCase() || '';
48
+ let score = 0;
49
+ const subStringMatchIndex = label.indexOf(searchQuery);
50
+ const isFullMatch = subStringMatchIndex !== -1;
51
+ if (isFullMatch) {
52
+ score = assignFullMatchScore(subStringMatchIndex, label);
53
+ }
54
+ if (noMatch(score) && option.synonyms) {
55
+ score = assignSynonymsScore(option, searchQuery);
56
+ }
57
+ if (noMatch(score) && (_config$item = config.item) !== null && _config$item !== void 0 && _config$item.extraFieldsToSearch) {
58
+ score = assignExtraFieldsScore(config, option, searchQuery);
59
+ }
60
+ return {
61
+ score,
62
+ label,
63
+ option
64
+ };
65
+ };
66
+ const scoreOptions = (searchQuery, labelMapper, config) => (scoredOptions, option) => {
67
+ const scoredOption = mapToScoreAndLabel(option, searchQuery, labelMapper, config);
68
+ if (scoredOption.score > 0) {
69
+ scoredOptions.push(scoredOption);
70
+ }
71
+ return scoredOptions;
72
+ };
73
+ const compareByScoreAndLabel = (r1, r2) => (r1.score - r2.score) * 16 + r1.label.localeCompare(r2.label);
14
74
  const getAutocompleteSource = config => {
15
75
  let options = [];
16
76
  (0, _getOptions.default)(config, val => {
17
77
  options = val;
18
78
  });
19
- const labelMaker = config !== null && config !== void 0 && config.item ? _copReactComponents.Utils.itemLabel(config.item) : null;
20
- return (query, populateResults) => {
21
- const lcQuery = query ? query.toLowerCase() : '';
22
-
23
- // go through all options and give them a grade
24
-
25
- const results = options.map(opt => {
26
- const label = labelMaker ? labelMaker(opt) : opt.label || '';
27
- const lcLabel = label.toLowerCase();
28
-
29
- // result 'score'
30
- let grade = 0;
31
-
32
- // highest result - match at start of string
33
- const index = lcLabel.indexOf(lcQuery);
34
- if (index === 0) {
35
- grade = 1;
36
- } else if (index > 0) {
37
- grade = lcLabel.substring(index - 1).startsWith(" ") ? 2 : 3;
38
- } else {
39
- var _config$item;
40
- if (opt.synonyms) {
41
- const match = opt.synonyms.some(synonym => synonym.toLowerCase().includes(lcQuery));
42
- if (match) {
43
- grade = 4;
44
- }
45
- }
46
- if (!grade && (_config$item = config.item) !== null && _config$item !== void 0 && _config$item.extraFieldsToSearch) {
47
- const found = config.item.extraFieldsToSearch.some(field => {
48
- if (typeof opt[field] !== 'string') {
49
- return false;
50
- }
51
- return opt[field].toLowerCase().includes(lcQuery);
52
- });
53
- if (found) grade = 5;
54
- }
55
- }
56
- return {
57
- grade,
58
- label,
59
- opt
60
- };
61
- }).filter(result => result.grade > 0);
62
-
63
- // sort results and then map to just opts
64
- populateResults(results
65
- // the sort prioritises grade but also sorts by string value
66
- .sort((r1, r2) => (r1.grade - r2.grade) * 16 + r1.label.localeCompare(r2.label)).map(r => r.opt));
79
+ const labelMapper = config !== null && config !== void 0 && config.item ? _copReactComponents.Utils.itemLabel(config.item) : null;
80
+ return (query, renderResult) => {
81
+ const searchQuery = typeof query === 'string' ? query.toLowerCase() : '';
82
+ const result = options.reduce(scoreOptions(searchQuery, labelMapper, config), []).sort(compareByScoreAndLabel).slice(0, MAX_NUMBER_RESULTS).map(scoredOptions => scoredOptions.option);
83
+ renderResult(result);
67
84
  };
68
85
  };
69
86
  var _default = exports.default = getAutocompleteSource;
@@ -6,10 +6,9 @@ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbol
6
6
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
7
7
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
8
8
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
9
- function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } // Local imports
9
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
10
10
  describe('utils', () => {
11
11
  describe('Data', () => {
12
- // test data
13
12
  const gbCurrency = {
14
13
  currencyName: 'Great British Pounds',
15
14
  currencyCode: 'GBP',
@@ -25,7 +24,7 @@ describe('utils', () => {
25
24
  value: 'USD',
26
25
  label: 'USD',
27
26
  slangTerm: 'bucks',
28
- synonyms: ['greenbacks']
27
+ synonyms: ['greenbacks', 11]
29
28
  };
30
29
  const options = {
31
30
  alpha: {
@@ -81,6 +80,15 @@ describe('utils', () => {
81
80
  }]);
82
81
  });
83
82
  });
83
+ it('should only return first 6000 results that have some type of match', () => {
84
+ const moreThanMaxNumberOfOptions = Array(6001).fill(options.alpha);
85
+ const config = {
86
+ data: {
87
+ options: moreThanMaxNumberOfOptions
88
+ }
89
+ };
90
+ (0, _getAutocompleteSource.default)(config)(options.alpha.label, results => expect(results.length).toEqual(6000));
91
+ });
84
92
  it('should handle an empty query', () => {
85
93
  const CONFIG = {
86
94
  data: {
@@ -117,6 +125,18 @@ describe('utils', () => {
117
125
  expect(results).toEqual([options.alpha, options.bravo]);
118
126
  });
119
127
  });
128
+ it('should handle a non string query', () => {
129
+ const CONFIG = {
130
+ data: {
131
+ options: [options.alpha, options.bravo]
132
+ }
133
+ };
134
+ const SOURCE = (0, _getAutocompleteSource.default)(CONFIG);
135
+ SOURCE(1, results => {
136
+ expect(results.length).toEqual(2);
137
+ expect(results).toEqual([options.alpha, options.bravo]);
138
+ });
139
+ });
120
140
  it('should handle a missing label on an option', () => {
121
141
  const CONFIG = {
122
142
  data: {
@@ -171,8 +191,7 @@ describe('utils', () => {
171
191
  item: {
172
192
  value: 'currencyCode',
173
193
  label: 'currencyCode',
174
- // eslint-disable-next-line no-template-curly-in-string
175
- format: '${currencyName} (${currencyCode})'
194
+ format: "${currencyName} (${currencyCode})"
176
195
  },
177
196
  data: {
178
197
  options: testCurrencies
@@ -194,8 +213,7 @@ describe('utils', () => {
194
213
  item: {
195
214
  value: 'currencyCode',
196
215
  label: 'currencyCode',
197
- // eslint-disable-next-line no-template-curly-in-string
198
- format: '${currencyName} (${currencyCode})'
216
+ format: "${currencyName} (${currencyCode})"
199
217
  },
200
218
  data: {
201
219
  options: testCurrencies
@@ -212,8 +230,7 @@ describe('utils', () => {
212
230
  item: {
213
231
  value: 'currencyCode',
214
232
  label: 'currencyCode',
215
- // eslint-disable-next-line no-template-curly-in-string
216
- format: '${currencyName} (${currencyCode})'
233
+ format: "${currencyName} (${currencyCode})"
217
234
  },
218
235
  data: {
219
236
  options: testCurrencies
@@ -230,8 +247,7 @@ describe('utils', () => {
230
247
  item: {
231
248
  value: 'currencyCode',
232
249
  label: 'currencyCode',
233
- // eslint-disable-next-line no-template-curly-in-string
234
- format: '${currencyName} (${currencyCode})'
250
+ format: "${currencyName} (${currencyCode})"
235
251
  },
236
252
  data: {
237
253
  options: testCurrencies
@@ -248,8 +264,7 @@ describe('utils', () => {
248
264
  item: {
249
265
  value: 'currencyCode',
250
266
  label: 'currencyCode',
251
- // eslint-disable-next-line no-template-curly-in-string
252
- format: '${currencyName} (${currencyCode})'
267
+ format: "${currencyName} (${currencyCode})"
253
268
  },
254
269
  data: {
255
270
  options: [gbCurrency, {
@@ -281,8 +296,7 @@ describe('utils', () => {
281
296
  item: {
282
297
  value: 'currencyCode',
283
298
  label: 'currencyCode',
284
- // eslint-disable-next-line no-template-curly-in-string
285
- format: '${currencyName} (${currencyCode})'
299
+ format: "${currencyName} (${currencyCode})"
286
300
  },
287
301
  data: {
288
302
  options: testCurrencies
@@ -300,9 +314,8 @@ describe('utils', () => {
300
314
  item: {
301
315
  value: 'currencyCode',
302
316
  label: 'currencyCode',
303
- // eslint-disable-next-line no-template-curly-in-string
304
- format: '${currencyName} (${currencyCode})',
305
- extraFieldsToSearch: ['slangTerm']
317
+ format: "${currencyName} (${currencyCode})",
318
+ extraFieldsToSearch: ['slangTerm', null]
306
319
  },
307
320
  data: {
308
321
  options: testCurrencies
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ukhomeoffice/cop-react-form-renderer",
3
- "version": "6.14.3-alpha",
3
+ "version": "6.14.3",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "clean": "rimraf dist",
@@ -16,7 +16,7 @@
16
16
  "yalc-publish": "yarn compile-with-maps && cp -r src dist/src && yalc publish --push"
17
17
  },
18
18
  "dependencies": {
19
- "@ukhomeoffice/cop-react-components": "4.7.6",
19
+ "@ukhomeoffice/cop-react-components": "4.7.7",
20
20
  "axios": "^0.23.0",
21
21
  "dayjs": "^1.11.0",
22
22
  "govuk-frontend": "^5.0.0",