@coveo/quantic 3.15.4-pre.da198077a0 → 3.15.4

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.
@@ -1027,8 +1027,8 @@
1027
1027
  "attribute": "answer-configuration-id",
1028
1028
  "access": "@api",
1029
1029
  "description": "<p>The unique identifier of the answer configuration to use to generate the answer.</p>",
1030
- "required": true,
1031
- "defaultValue": "",
1030
+ "required": false,
1031
+ "defaultValue": "{undefined}",
1032
1032
  "type": "string"
1033
1033
  },
1034
1034
  {
@@ -311,6 +311,11 @@ export default class QuanticCategoryFacet extends LightningElement {
311
311
  facetId: this.facet.state.facetId,
312
312
  format: this.remoteGetValueCaption,
313
313
  element: this.template.host,
314
+ metadata: {
315
+ customCaptions: Object.entries(this.customCaptions).map(
316
+ ([key, label]) => ({value: key, caption: label})
317
+ ),
318
+ },
314
319
  });
315
320
  };
316
321
 
@@ -313,6 +313,11 @@ export default class QuanticFacet extends LightningElement {
313
313
  facetId: this.facet.state.facetId,
314
314
  format: this.remoteGetValueCaption,
315
315
  element: this.template.host,
316
+ metadata: {
317
+ customCaptions: Object.entries(this.customCaptions).map(
318
+ ([key, label]) => ({value: key, caption: label})
319
+ ),
320
+ },
316
321
  });
317
322
  if (this.dependsOn) {
318
323
  this.validateDependsOnProperty();
@@ -92,6 +92,7 @@ export default class QuanticGeneratedAnswer extends LightningElement {
92
92
  * The unique identifier of the answer configuration to use to generate the answer.
93
93
  * @api
94
94
  * @type {string}
95
+ * @default {undefined}
95
96
  */
96
97
  @api answerConfigurationId;
97
98
  /**
@@ -0,0 +1,501 @@
1
+ /* eslint-disable no-import-assign */
2
+ // @ts-ignore
3
+ import {createElement} from 'lwc';
4
+ import QuanticRefineModalContent from 'c/quanticRefineModalContent';
5
+ import * as mockHeadlessLoader from 'c/quanticHeadlessLoader';
6
+
7
+ jest.mock('c/quanticHeadlessLoader');
8
+
9
+ /** @type {{disableDynamicNavigation: boolean}} */
10
+ const defaultOptions = {
11
+ disableDynamicNavigation: false,
12
+ };
13
+
14
+ function createTestComponent(options = defaultOptions) {
15
+ const element = createElement('c-quantic-refine-modal-content', {
16
+ is: QuanticRefineModalContent,
17
+ });
18
+ for (const [key, value] of Object.entries(options)) {
19
+ element[key] = value;
20
+ }
21
+
22
+ document.body.appendChild(element);
23
+ return element;
24
+ }
25
+
26
+ const selectors = {
27
+ quanticFacet: 'c-quantic-facet',
28
+ quanticCategoryFacet: 'c-quantic-category-facet',
29
+ quanticFacetCaption: 'c-quantic-facet-caption',
30
+ };
31
+
32
+ const initialSearchStatusState = {
33
+ hasError: false,
34
+ };
35
+ let searchStatusState = initialSearchStatusState;
36
+
37
+ const initialBreadcrumbManagerState = {};
38
+ let breadcrumbManagerState = initialBreadcrumbManagerState;
39
+
40
+ const functionsMocks = {
41
+ buildBreadcrumbManager: jest.fn(() => ({
42
+ state: breadcrumbManagerState,
43
+ subscribe: functionsMocks.breadcrumbManagerStateSubscriber,
44
+ })),
45
+ buildSearchStatus: jest.fn(() => ({
46
+ state: searchStatusState,
47
+ subscribe: functionsMocks.searchStatusStateSubscriber,
48
+ })),
49
+ breadcrumbManagerStateSubscriber: jest.fn((cb) => {
50
+ cb();
51
+ return functionsMocks.breadcrumbManagerStateUnsubscriber;
52
+ }),
53
+ searchStatusStateSubscriber: jest.fn((cb) => {
54
+ cb();
55
+ return functionsMocks.searchStatusStateUnsubscriber;
56
+ }),
57
+ breadcrumbManagerStateUnsubscriber: jest.fn(),
58
+ searchStatusStateUnsubscriber: jest.fn(),
59
+ retry: jest.fn(),
60
+ };
61
+
62
+ // Helper function to wait until the microtask queue is empty.
63
+ function flushPromises() {
64
+ // eslint-disable-next-line @lwc/lwc/no-async-operation
65
+ return new Promise((resolve) => setTimeout(resolve, 0));
66
+ }
67
+
68
+ const exampleEngine = {
69
+ id: 'dummy engine',
70
+ };
71
+ let isInitialized = false;
72
+
73
+ function prepareHeadlessState() {
74
+ // @ts-ignore
75
+ mockHeadlessLoader.getHeadlessBundle = () => {
76
+ return {
77
+ buildBreadcrumbManager: functionsMocks.buildBreadcrumbManager,
78
+ buildSearchStatus: functionsMocks.buildSearchStatus,
79
+ };
80
+ };
81
+ }
82
+
83
+ function prepareQuanticStore(facetData) {
84
+ // @ts-ignore
85
+ mockHeadlessLoader.getAllFacetsFromStore = () => {
86
+ return facetData;
87
+ };
88
+ }
89
+
90
+ function mockSuccessfulHeadlessInitialization() {
91
+ // @ts-ignore
92
+ mockHeadlessLoader.initializeWithHeadless = (element, _, initialize) => {
93
+ if (element instanceof QuanticRefineModalContent && !isInitialized) {
94
+ isInitialized = true;
95
+ initialize(exampleEngine);
96
+ }
97
+ };
98
+ }
99
+
100
+ function cleanup() {
101
+ // The jsdom instance is shared across test cases in a single file so reset the DOM
102
+ while (document.body.firstChild) {
103
+ document.body.removeChild(document.body.firstChild);
104
+ }
105
+ jest.clearAllMocks();
106
+ isInitialized = false;
107
+ }
108
+
109
+ describe('c-quantic-refine-modal-content', () => {
110
+ afterEach(() => {
111
+ cleanup();
112
+ });
113
+
114
+ describe('when the dynamic navigation feature is enabled', () => {
115
+ describe('standard facet', () => {
116
+ const exampleFacetAttributes = {
117
+ facetId: 'filetype',
118
+ field: 'filetype',
119
+ label: 'File Type',
120
+ numberOfValues: '8',
121
+ sortCriteria: 'occurrences',
122
+ noSearch: 'true',
123
+ displayValuesAs: 'link',
124
+ noFilterFacetCount: 'false',
125
+ injectionDepth: '1000',
126
+ customSort: 'YouTubeVideo,PDF,HTML',
127
+ dependsOn: '',
128
+ };
129
+
130
+ beforeEach(() => {
131
+ mockSuccessfulHeadlessInitialization();
132
+ prepareHeadlessState();
133
+ prepareQuanticStore({
134
+ filetype: {
135
+ label: 'File Type',
136
+ facetId: 'filetype',
137
+ element: {
138
+ localName: 'c-quantic-facet',
139
+ ...exampleFacetAttributes,
140
+ },
141
+ },
142
+ });
143
+ });
144
+
145
+ it('should display the quantic facet with its attributes and without custom captions', async () => {
146
+ const element = createTestComponent();
147
+ await flushPromises();
148
+
149
+ const quanticFacet = element.shadowRoot.querySelector(
150
+ selectors.quanticFacet
151
+ );
152
+ expect(quanticFacet).not.toBeNull();
153
+
154
+ Object.entries(exampleFacetAttributes).forEach(([key, value]) => {
155
+ expect(quanticFacet[key]).toBe(value);
156
+ });
157
+
158
+ const quanticFacetCaption = element.shadowRoot.querySelector(
159
+ selectors.quanticFacetCaption
160
+ );
161
+ expect(quanticFacetCaption).toBeNull();
162
+ });
163
+
164
+ describe('when a standard facet is registered in the store with custom captions', () => {
165
+ const exampleCaption = {
166
+ value: 'YouTubeVideo',
167
+ caption: 'YouTube Video',
168
+ };
169
+
170
+ beforeEach(() => {
171
+ mockSuccessfulHeadlessInitialization();
172
+ prepareHeadlessState();
173
+ prepareQuanticStore({
174
+ filetype: {
175
+ label: 'File Type',
176
+ facetId: 'filetype',
177
+ element: {
178
+ localName: 'c-quantic-facet',
179
+ ...exampleFacetAttributes,
180
+ },
181
+ metadata: {
182
+ customCaptions: [exampleCaption],
183
+ },
184
+ },
185
+ });
186
+ });
187
+
188
+ it('should display the quantic facet with its custom captions and attributes', async () => {
189
+ const element = createTestComponent();
190
+ await flushPromises();
191
+
192
+ const quanticFacet = element.shadowRoot.querySelector(
193
+ selectors.quanticFacet
194
+ );
195
+ expect(quanticFacet).not.toBeNull();
196
+
197
+ const quanticFacetCaption = element.shadowRoot.querySelector(
198
+ selectors.quanticFacetCaption
199
+ );
200
+ expect(quanticFacetCaption).not.toBeNull();
201
+ expect(quanticFacetCaption.value).toBe(exampleCaption.value);
202
+ expect(quanticFacetCaption.caption).toBe(exampleCaption.caption);
203
+ });
204
+ });
205
+ });
206
+
207
+ describe('category facet', () => {
208
+ const exampleCategoryFacetAttributes = {
209
+ facetId: 'country',
210
+ field: 'country',
211
+ label: 'Country',
212
+ basePath: 'North America',
213
+ noFilterByBasePath: 'false',
214
+ noFilterFacetCount: 'false',
215
+ delimitingCharacter: '>',
216
+ numberOfValues: '10',
217
+ sortCriteria: 'alphanumeric',
218
+ withSearch: 'true',
219
+ dependsOn: '',
220
+ };
221
+
222
+ beforeEach(() => {
223
+ mockSuccessfulHeadlessInitialization();
224
+ prepareHeadlessState();
225
+ prepareQuanticStore({
226
+ country: {
227
+ label: 'Country',
228
+ facetId: 'country',
229
+ element: {
230
+ localName: 'c-quantic-category-facet',
231
+ ...exampleCategoryFacetAttributes,
232
+ },
233
+ },
234
+ });
235
+ });
236
+
237
+ it('should display the quantic category facet with its attributes and without custom captions', async () => {
238
+ const element = createTestComponent();
239
+ await flushPromises();
240
+
241
+ const quanticCategoryFacet = element.shadowRoot.querySelector(
242
+ selectors.quanticCategoryFacet
243
+ );
244
+ expect(quanticCategoryFacet).not.toBeNull();
245
+
246
+ Object.entries(exampleCategoryFacetAttributes).forEach(
247
+ ([key, value]) => {
248
+ expect(quanticCategoryFacet[key]).toBe(value);
249
+ }
250
+ );
251
+
252
+ const quanticFacetCaption = element.shadowRoot.querySelector(
253
+ selectors.quanticFacetCaption
254
+ );
255
+ expect(quanticFacetCaption).toBeNull();
256
+ });
257
+
258
+ describe('when a category facet is registered in the store with custom captions', () => {
259
+ const exampleCaption = {
260
+ value: 'Ca',
261
+ caption: 'Canada',
262
+ };
263
+
264
+ beforeEach(() => {
265
+ mockSuccessfulHeadlessInitialization();
266
+ prepareHeadlessState();
267
+ prepareQuanticStore({
268
+ country: {
269
+ label: 'Country',
270
+ facetId: 'country',
271
+ element: {
272
+ localName: 'c-quantic-category-facet',
273
+ ...exampleCategoryFacetAttributes,
274
+ },
275
+ metadata: {
276
+ customCaptions: [exampleCaption],
277
+ },
278
+ },
279
+ });
280
+ });
281
+
282
+ it('should display the quantic category facet with its custom captions', async () => {
283
+ const element = createTestComponent();
284
+ await flushPromises();
285
+
286
+ const quanticCategoryFacet = element.shadowRoot.querySelector(
287
+ selectors.quanticCategoryFacet
288
+ );
289
+ expect(quanticCategoryFacet).not.toBeNull();
290
+
291
+ const quanticFacetCaption = element.shadowRoot.querySelector(
292
+ selectors.quanticFacetCaption
293
+ );
294
+ expect(quanticFacetCaption).not.toBeNull();
295
+ expect(quanticFacetCaption.value).toBe(exampleCaption.value);
296
+ expect(quanticFacetCaption.caption).toBe(exampleCaption.caption);
297
+ });
298
+ });
299
+ });
300
+ });
301
+
302
+ describe('when the dynamic navigation feature is disabled', () => {
303
+ describe('standard facet', () => {
304
+ const exampleFacetAttributes = {
305
+ facetId: 'filetype',
306
+ field: 'filetype',
307
+ label: 'File Type',
308
+ numberOfValues: '8',
309
+ sortCriteria: 'occurrences',
310
+ noSearch: 'true',
311
+ displayValuesAs: 'link',
312
+ noFilterFacetCount: 'false',
313
+ injectionDepth: '1000',
314
+ customSort: 'YouTubeVideo,PDF,HTML',
315
+ dependsOn: '',
316
+ };
317
+
318
+ beforeEach(() => {
319
+ mockSuccessfulHeadlessInitialization();
320
+ prepareHeadlessState();
321
+ prepareQuanticStore({
322
+ filetype: {
323
+ label: 'File Type',
324
+ facetId: 'filetype',
325
+ element: {
326
+ localName: 'c-quantic-facet',
327
+ ...exampleFacetAttributes,
328
+ },
329
+ },
330
+ });
331
+ });
332
+
333
+ it('should display the quantic facet with its attributes and without custom captions', async () => {
334
+ const element = createTestComponent({
335
+ ...defaultOptions,
336
+ disableDynamicNavigation: true,
337
+ });
338
+ await flushPromises();
339
+
340
+ const quanticFacet = element.shadowRoot.querySelector(
341
+ selectors.quanticFacet
342
+ );
343
+ expect(quanticFacet).not.toBeNull();
344
+
345
+ Object.entries(exampleFacetAttributes).forEach(([key, value]) => {
346
+ expect(quanticFacet[key]).toBe(value);
347
+ });
348
+
349
+ const quanticFacetCaption = element.shadowRoot.querySelector(
350
+ selectors.quanticFacetCaption
351
+ );
352
+ expect(quanticFacetCaption).toBeNull();
353
+ });
354
+
355
+ describe('when a standard facet is registered in the store with custom captions', () => {
356
+ const exampleCaption = {
357
+ value: 'YouTubeVideo',
358
+ caption: 'YouTube Video',
359
+ };
360
+
361
+ beforeEach(() => {
362
+ mockSuccessfulHeadlessInitialization();
363
+ prepareHeadlessState();
364
+ prepareQuanticStore({
365
+ filetype: {
366
+ label: 'File Type',
367
+ facetId: 'filetype',
368
+ element: {
369
+ localName: 'c-quantic-facet',
370
+ ...exampleFacetAttributes,
371
+ },
372
+ metadata: {
373
+ customCaptions: [exampleCaption],
374
+ },
375
+ },
376
+ });
377
+ });
378
+
379
+ it('should display the quantic facet with its custom captions and attributes', async () => {
380
+ const element = createTestComponent({
381
+ ...defaultOptions,
382
+ disableDynamicNavigation: true,
383
+ });
384
+ await flushPromises();
385
+
386
+ const quanticFacet = element.shadowRoot.querySelector(
387
+ selectors.quanticFacet
388
+ );
389
+ expect(quanticFacet).not.toBeNull();
390
+
391
+ const quanticFacetCaption = element.shadowRoot.querySelector(
392
+ selectors.quanticFacetCaption
393
+ );
394
+ expect(quanticFacetCaption).not.toBeNull();
395
+ expect(quanticFacetCaption.value).toBe(exampleCaption.value);
396
+ expect(quanticFacetCaption.caption).toBe(exampleCaption.caption);
397
+ });
398
+ });
399
+ });
400
+
401
+ describe('category facet', () => {
402
+ const exampleCategoryFacetAttributes = {
403
+ facetId: 'country',
404
+ field: 'country',
405
+ label: 'Country',
406
+ basePath: 'North America',
407
+ noFilterByBasePath: 'false',
408
+ noFilterFacetCount: 'false',
409
+ delimitingCharacter: '>',
410
+ numberOfValues: '10',
411
+ sortCriteria: 'alphanumeric',
412
+ withSearch: 'true',
413
+ dependsOn: '',
414
+ };
415
+
416
+ beforeEach(() => {
417
+ mockSuccessfulHeadlessInitialization();
418
+ prepareHeadlessState();
419
+ prepareQuanticStore({
420
+ country: {
421
+ label: 'Country',
422
+ facetId: 'country',
423
+ element: {
424
+ localName: 'c-quantic-category-facet',
425
+ ...exampleCategoryFacetAttributes,
426
+ },
427
+ },
428
+ });
429
+ });
430
+
431
+ it('should display the quantic category facet with its attributes and without custom captions', async () => {
432
+ const element = createTestComponent({
433
+ ...defaultOptions,
434
+ disableDynamicNavigation: true,
435
+ });
436
+ await flushPromises();
437
+
438
+ const quanticCategoryFacet = element.shadowRoot.querySelector(
439
+ selectors.quanticCategoryFacet
440
+ );
441
+ expect(quanticCategoryFacet).not.toBeNull();
442
+
443
+ Object.entries(exampleCategoryFacetAttributes).forEach(
444
+ ([key, value]) => {
445
+ expect(quanticCategoryFacet[key]).toBe(value);
446
+ }
447
+ );
448
+
449
+ const quanticFacetCaption = element.shadowRoot.querySelector(
450
+ selectors.quanticFacetCaption
451
+ );
452
+ expect(quanticFacetCaption).toBeNull();
453
+ });
454
+
455
+ describe('when a category facet is registered in the store with custom captions', () => {
456
+ const exampleCaption = {
457
+ value: 'Ca',
458
+ caption: 'Canada',
459
+ };
460
+
461
+ beforeEach(() => {
462
+ mockSuccessfulHeadlessInitialization();
463
+ prepareHeadlessState();
464
+ prepareQuanticStore({
465
+ country: {
466
+ label: 'Country',
467
+ facetId: 'country',
468
+ element: {
469
+ localName: 'c-quantic-category-facet',
470
+ ...exampleCategoryFacetAttributes,
471
+ },
472
+ metadata: {
473
+ customCaptions: [exampleCaption],
474
+ },
475
+ },
476
+ });
477
+ });
478
+
479
+ it('should display the quantic category facet with its custom captions', async () => {
480
+ const element = createTestComponent({
481
+ ...defaultOptions,
482
+ disableDynamicNavigation: true,
483
+ });
484
+ await flushPromises();
485
+
486
+ const quanticCategoryFacet = element.shadowRoot.querySelector(
487
+ selectors.quanticCategoryFacet
488
+ );
489
+ expect(quanticCategoryFacet).not.toBeNull();
490
+
491
+ const quanticFacetCaption = element.shadowRoot.querySelector(
492
+ selectors.quanticFacetCaption
493
+ );
494
+ expect(quanticFacetCaption).not.toBeNull();
495
+ expect(quanticFacetCaption.value).toBe(exampleCaption.value);
496
+ expect(quanticFacetCaption.caption).toBe(exampleCaption.caption);
497
+ });
498
+ });
499
+ });
500
+ });
501
+ });
@@ -184,6 +184,12 @@ export default class QuanticRefineModalContent extends LightningElement {
184
184
  toDefaultFacet = (facetObject) => {
185
185
  return {
186
186
  isDefault: true,
187
+ customCaptions: facetObject.metadata?.customCaptions?.map?.(
188
+ (caption, index) => ({
189
+ ...caption,
190
+ index,
191
+ })
192
+ ),
187
193
  ...this.extractFacetDataFromElement(
188
194
  facetObject.element,
189
195
  QuanticFacet.attributes
@@ -199,6 +205,12 @@ export default class QuanticRefineModalContent extends LightningElement {
199
205
  toCategoryFacet = (facetObject) => {
200
206
  return {
201
207
  isCategory: true,
208
+ customCaptions: facetObject.metadata?.customCaptions?.map?.(
209
+ (caption, index) => ({
210
+ ...caption,
211
+ index,
212
+ })
213
+ ),
202
214
  ...this.extractFacetDataFromElement(
203
215
  facetObject.element,
204
216
  QuanticCategoryFacet.attributes
@@ -272,7 +284,7 @@ export default class QuanticRefineModalContent extends LightningElement {
272
284
  * @returns {Array<SortOption>}
273
285
  */
274
286
  get sortOptions() {
275
- return this.sortData.length > 0 ? this.sortData : [];
287
+ return this.sortData?.length > 0 ? this.sortData : [];
276
288
  }
277
289
 
278
290
  get shouldDisplayFiltersTitle() {
@@ -72,7 +72,16 @@
72
72
  key={facet.field}
73
73
  depends-on={facet.dependsOn}
74
74
  is-collapsed
75
- ></c-quantic-category-facet>
75
+ >
76
+ <template for:each={facet.customCaptions} for:item="caption">
77
+ <c-quantic-facet-caption
78
+ key={caption.index}
79
+ slot="captions"
80
+ value={caption.value}
81
+ caption={caption.caption}
82
+ ></c-quantic-facet-caption>
83
+ </template>
84
+ </c-quantic-category-facet>
76
85
  </template>
77
86
  <template if:true={facet.isTimeframe}>
78
87
  <c-quantic-timeframe-facet
@@ -127,7 +136,16 @@
127
136
  custom-sort={facet.customSort}
128
137
  depends-on={facet.dependsOn}
129
138
  is-collapsed
130
- ></c-quantic-facet>
139
+ >
140
+ <template for:each={facet.customCaptions} for:item="caption">
141
+ <c-quantic-facet-caption
142
+ key={caption.index}
143
+ slot="captions"
144
+ value={caption.value}
145
+ caption={caption.caption}
146
+ ></c-quantic-facet-caption>
147
+ </template>
148
+ </c-quantic-facet>
131
149
  </template>
132
150
  </template>
133
151
  </template>
@@ -73,7 +73,16 @@
73
73
  key={facet.field}
74
74
  depends-on={facet.dependsOn}
75
75
  is-collapsed
76
- ></c-quantic-category-facet>
76
+ >
77
+ <template for:each={facet.customCaptions} for:item="caption">
78
+ <c-quantic-facet-caption
79
+ key={caption.index}
80
+ slot="captions"
81
+ value={caption.value}
82
+ caption={caption.caption}
83
+ ></c-quantic-facet-caption>
84
+ </template>
85
+ </c-quantic-category-facet>
77
86
  </template>
78
87
  <template if:true={facet.isTimeframe}>
79
88
  <c-quantic-timeframe-facet
@@ -128,7 +137,16 @@
128
137
  custom-sort={facet.customSort}
129
138
  depends-on={facet.dependsOn}
130
139
  is-collapsed
131
- ></c-quantic-facet>
140
+ >
141
+ <template for:each={facet.customCaptions} for:item="caption">
142
+ <c-quantic-facet-caption
143
+ key={caption.index}
144
+ slot="captions"
145
+ value={caption.value}
146
+ caption={caption.caption}
147
+ ></c-quantic-facet-caption>
148
+ </template>
149
+ </c-quantic-facet>
132
150
  </template>
133
151
  </template>
134
152
  </c-quantic-facet-manager>