@coveo/quantic 3.10.0-pre.c5a50cf39c → 3.10.0-pre.d66d49ebbe

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.
Files changed (31) hide show
  1. package/docs/out/quantic-docs.json +38 -2
  2. package/force-app/main/default/lwc/quanticCaseAssistInterface/quanticCaseAssistInterface.js +8 -0
  3. package/force-app/main/default/lwc/quanticCategoryFacet/__tests__/quanticCategoryFacet.test.js +108 -1
  4. package/force-app/main/default/lwc/quanticCategoryFacet/quanticCategoryFacet.js +22 -0
  5. package/force-app/main/default/lwc/quanticDateFacet/__tests__/quanticDateFacet.test.js +107 -1
  6. package/force-app/main/default/lwc/quanticDateFacet/quanticDateFacet.js +22 -0
  7. package/force-app/main/default/lwc/quanticDidYouMean/__tests__/quanticDidYouMean.test.js +199 -0
  8. package/force-app/main/default/lwc/quanticDidYouMean/quanticDidYouMean.js +24 -2
  9. package/force-app/main/default/lwc/quanticFacet/__tests__/quanticFacet.test.js +106 -1
  10. package/force-app/main/default/lwc/quanticFacet/quanticFacet.js +21 -1
  11. package/force-app/main/default/lwc/quanticInsightInterface/quanticInsightInterface.js +8 -0
  12. package/force-app/main/default/lwc/quanticNumericFacet/__tests__/quanticNumericFacet.test.js +107 -1
  13. package/force-app/main/default/lwc/quanticNumericFacet/quanticNumericFacet.js +22 -0
  14. package/force-app/main/default/lwc/quanticRecommendationInterface/quanticRecommendationInterface.js +8 -0
  15. package/force-app/main/default/lwc/quanticSearchInterface/quanticSearchInterface.js +8 -0
  16. package/force-app/main/default/lwc/quanticTimeframeFacet/__tests__/quanticTimeframeFacet.test.js +107 -1
  17. package/force-app/main/default/lwc/quanticTimeframeFacet/quanticTimeframeFacet.js +22 -0
  18. package/force-app/main/default/staticresources/coveoheadless/case-assist/headless.js +7 -5
  19. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/instant-products/headless-instant-products.ssr.d.ts +1 -1
  20. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/core/interactive-result/headless-core-interactive-result.d.ts +2 -2
  21. package/force-app/main/default/staticresources/coveoheadless/definitions/features/analytics/search-action-cause.d.ts +0 -82
  22. package/force-app/main/default/staticresources/coveoheadless/definitions/features/facets/facet-set/facet-set-analytics-actions.d.ts +0 -1
  23. package/force-app/main/default/staticresources/coveoheadless/definitions/features/history/history-analytics-actions.d.ts +0 -3
  24. package/force-app/main/default/staticresources/coveoheadless/definitions/features/search/search-actions.d.ts +2 -2
  25. package/force-app/main/default/staticresources/coveoheadless/definitions/features/static-filter-set/static-filter-set-actions.d.ts +0 -4
  26. package/force-app/main/default/staticresources/coveoheadless/definitions/ssr-commerce.index.d.ts +4 -2
  27. package/force-app/main/default/staticresources/coveoheadless/headless.js +9 -7
  28. package/force-app/main/default/staticresources/coveoheadless/insight/headless.js +8 -6
  29. package/force-app/main/default/staticresources/coveoheadless/recommendation/headless.js +7 -5
  30. package/force-app/main/default/staticresources/dompurify/purify.min.js +1339 -2
  31. package/package.json +4 -4
@@ -382,7 +382,7 @@
382
382
  ],
383
383
  "fires": "",
384
384
  "examples": [
385
- "<c-quantic-did-you-mean engine-id={engineId}></c-quantic-did-you-mean>"
385
+ "<c-quantic-did-you-mean engine-id={engineId} disable-query-auto-correction={disableQueryAutoCorrection} query-correction-mode=\"next\"></c-quantic-did-you-mean>"
386
386
  ],
387
387
  "xmlMeta": {
388
388
  "apiVersion": "53.0",
@@ -398,6 +398,24 @@
398
398
  "required": true,
399
399
  "defaultValue": "",
400
400
  "type": "string"
401
+ },
402
+ {
403
+ "name": "disableQueryAutoCorrection",
404
+ "attribute": "disable-query-auto-correction",
405
+ "access": "@api",
406
+ "description": "<p>Whether to disable automatically applying corrections for queries that would otherwise return no results.\nWhen <code>disableQueryAutoCorrection</code> is <code>false</code>, the component automatically triggers a new query using the suggested term.\nWhen <code>disableQueryAutoCorrection</code> is <code>true</code>, the component returns the suggested term without triggering a new query.</p>",
407
+ "required": false,
408
+ "defaultValue": "false",
409
+ "type": "boolean"
410
+ },
411
+ {
412
+ "name": "queryCorrectionMode",
413
+ "attribute": "query-correction-mode",
414
+ "access": "@api",
415
+ "description": "<p>Defines which query correction system to use.\n<code>legacy</code>: Query correction is powered by the legacy index system. This system relies on an algorithm using solely the index content to compute the suggested terms.\n<code>next</code>: Query correction is powered by a machine learning system, requiring a valid query suggestion model configured in your Coveo environment to function properly. This system relies on machine learning algorithms to compute the suggested terms.\nThe <code>legacy</code> system will send two requests to the API to get the suggestions, while the <code>next</code> system will send one request.</p>",
416
+ "required": false,
417
+ "defaultValue": "'legacy'",
418
+ "type": "string"
401
419
  }
402
420
  ]
403
421
  },
@@ -4315,7 +4333,7 @@
4315
4333
  ],
4316
4334
  "fires": "",
4317
4335
  "examples": [
4318
- "<c-quantic-did-you-mean engine-id={engineId}></c-quantic-did-you-mean>"
4336
+ "<c-quantic-did-you-mean engine-id={engineId} disable-query-auto-correction={disableQueryAutoCorrection} query-correction-mode=\"next\"></c-quantic-did-you-mean>"
4319
4337
  ],
4320
4338
  "xmlMeta": {
4321
4339
  "apiVersion": "53.0",
@@ -4331,6 +4349,24 @@
4331
4349
  "required": true,
4332
4350
  "defaultValue": "",
4333
4351
  "type": "string"
4352
+ },
4353
+ {
4354
+ "name": "disableQueryAutoCorrection",
4355
+ "attribute": "disable-query-auto-correction",
4356
+ "access": "@api",
4357
+ "description": "<p>Whether to disable automatically applying corrections for queries that would otherwise return no results.\nWhen <code>disableQueryAutoCorrection</code> is <code>false</code>, the component automatically triggers a new query using the suggested term.\nWhen <code>disableQueryAutoCorrection</code> is <code>true</code>, the component returns the suggested term without triggering a new query.</p>",
4358
+ "required": false,
4359
+ "defaultValue": "false",
4360
+ "type": "boolean"
4361
+ },
4362
+ {
4363
+ "name": "queryCorrectionMode",
4364
+ "attribute": "query-correction-mode",
4365
+ "access": "@api",
4366
+ "description": "<p>Defines which query correction system to use.\n<code>legacy</code>: Query correction is powered by the legacy index system. This system relies on an algorithm using solely the index content to compute the suggested terms.\n<code>next</code>: Query correction is powered by a machine learning system, requiring a valid query suggestion model configured in your Coveo environment to function properly. This system relies on machine learning algorithms to compute the suggested terms.\nThe <code>legacy</code> system will send two requests to the API to get the suggestions, while the <code>next</code> system will send one request.</p>",
4367
+ "required": false,
4368
+ "defaultValue": "'legacy'",
4369
+ "type": "string"
4334
4370
  }
4335
4371
  ]
4336
4372
  },
@@ -64,6 +64,14 @@ export default class QuanticCaseAssistInterface extends LightningElement {
64
64
  ...(document.referrer && {
65
65
  originLevel3: document.referrer.substring(0, 256),
66
66
  }),
67
+ analyticsClientMiddleware: (_event, payload) => {
68
+ if (!payload.customData) {
69
+ payload.customData = {};
70
+ }
71
+ payload.customData.coveoQuanticVersion =
72
+ window.coveoQuanticVersion;
73
+ return payload;
74
+ },
67
75
  },
68
76
  ...rest,
69
77
  },
@@ -15,16 +15,21 @@ jest.mock('c/quanticUtils', () => ({
15
15
  CATEGORYFACETS: 'categoryFacets',
16
16
  },
17
17
  },
18
+ I18nUtils: {
19
+ format: jest.fn(),
20
+ },
18
21
  }));
19
22
  jest.mock('c/quanticHeadlessLoader');
20
23
 
21
24
  const selectors = {
22
25
  facetContent: '[data-test="facet-content"]',
26
+ componentError: 'c-quantic-component-error',
23
27
  };
24
28
 
25
29
  const exampleFacetId = 'example facet id';
30
+ const exampleField = 'exampleField';
26
31
  const defaultOptions = {
27
- field: 'example field',
32
+ field: exampleField,
28
33
  };
29
34
  const categoryFacetControllerMock = {
30
35
  subscribe: jest.fn((callback) => callback()),
@@ -33,6 +38,8 @@ const categoryFacetControllerMock = {
33
38
  values: [],
34
39
  },
35
40
  };
41
+ const parentFacetIdError = `The ${exampleField} c-quantic-category-facet requires dependsOn.parentFacetId to be a valid string.`;
42
+ const expectedValueError = `The ${exampleField} c-quantic-category-facet requires dependsOn.expectedValue to be a valid string.`;
36
43
 
37
44
  function createTestComponent(options = defaultOptions) {
38
45
  prepareHeadlessState();
@@ -82,6 +89,21 @@ const exampleEngine = {
82
89
  };
83
90
  let isInitialized = false;
84
91
 
92
+ function mockBueno() {
93
+ // @ts-ignore
94
+ mockHeadlessLoader.getBueno = () => {
95
+ // @ts-ignore
96
+ global.Bueno = {
97
+ isString: jest
98
+ .fn()
99
+ .mockImplementation(
100
+ (value) => Object.prototype.toString.call(value) === '[object String]'
101
+ ),
102
+ };
103
+ return new Promise((resolve) => resolve());
104
+ };
105
+ }
106
+
85
107
  function mockSuccessfulHeadlessInitialization() {
86
108
  // @ts-ignore
87
109
  mockHeadlessLoader.initializeWithHeadless = (element, _, initialize) => {
@@ -104,6 +126,7 @@ function cleanup() {
104
126
  describe('c-quantic-category-facet', () => {
105
127
  beforeAll(() => {
106
128
  mockSuccessfulHeadlessInitialization();
129
+ mockBueno();
107
130
  });
108
131
 
109
132
  afterEach(() => {
@@ -198,6 +221,90 @@ describe('c-quantic-category-facet', () => {
198
221
  });
199
222
  });
200
223
 
224
+ describe('validation of the dependsOn property', () => {
225
+ let consoleError;
226
+ beforeAll(() => {
227
+ consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
228
+ });
229
+
230
+ describe('when dependsOn.parentFacetId is not provided', () => {
231
+ it('should display the error component', async () => {
232
+ const invalidFacetDependency = {
233
+ expectedValue: 'txt',
234
+ };
235
+ const element = createTestComponent({
236
+ ...defaultOptions,
237
+ dependsOn: invalidFacetDependency,
238
+ });
239
+ await flushPromises();
240
+
241
+ const componentError = element.shadowRoot.querySelector(
242
+ selectors.componentError
243
+ );
244
+ const facetContent = element.shadowRoot.querySelector(
245
+ selectors.facetContent
246
+ );
247
+
248
+ expect(consoleError).toHaveBeenCalledTimes(1);
249
+ expect(consoleError).toHaveBeenCalledWith(parentFacetIdError);
250
+ expect(componentError).not.toBeNull();
251
+ expect(facetContent).toBeNull();
252
+ });
253
+ });
254
+
255
+ describe('when dependsOn.parentFacetId is not a string', () => {
256
+ it('should display the error component', async () => {
257
+ const invalidFacetDependency = {
258
+ parentFacetId: 1,
259
+ expectedValue: 'txt',
260
+ };
261
+ const element = createTestComponent({
262
+ ...defaultOptions,
263
+ dependsOn: invalidFacetDependency,
264
+ });
265
+ await flushPromises();
266
+
267
+ const componentError = element.shadowRoot.querySelector(
268
+ selectors.componentError
269
+ );
270
+ const facetContent = element.shadowRoot.querySelector(
271
+ selectors.facetContent
272
+ );
273
+
274
+ expect(consoleError).toHaveBeenCalledTimes(1);
275
+ expect(consoleError).toHaveBeenCalledWith(parentFacetIdError);
276
+ expect(componentError).not.toBeNull();
277
+ expect(facetContent).toBeNull();
278
+ });
279
+ });
280
+
281
+ describe('when dependsOn.expectedValue is not a string', () => {
282
+ it('should display the error component', async () => {
283
+ const invalidFacetDependency = {
284
+ parentFacetId: 'filetype',
285
+ expectedValue: 2,
286
+ };
287
+ const element = createTestComponent({
288
+ ...defaultOptions,
289
+ dependsOn: invalidFacetDependency,
290
+ });
291
+ await flushPromises();
292
+
293
+ const componentError = element.shadowRoot.querySelector(
294
+ selectors.componentError
295
+ );
296
+ const facetContent = element.shadowRoot.querySelector(
297
+ selectors.facetContent
298
+ );
299
+
300
+ expect(consoleError).toHaveBeenCalledTimes(1);
301
+ expect(consoleError).toHaveBeenCalledWith(expectedValueError);
302
+ expect(componentError).not.toBeNull();
303
+ expect(facetContent).toBeNull();
304
+ });
305
+ });
306
+ });
307
+
201
308
  describe('when the component is disconnected', () => {
202
309
  it('should make the condition manager stop watching the facet', async () => {
203
310
  const exampleFacetDependency = {
@@ -14,6 +14,7 @@ import {
14
14
  initializeWithHeadless,
15
15
  registerToStore,
16
16
  getHeadlessBundle,
17
+ getBueno,
17
18
  } from 'c/quanticHeadlessLoader';
18
19
  import {
19
20
  I18nUtils,
@@ -301,6 +302,7 @@ export default class QuanticCategoryFacet extends LightningElement {
301
302
  },
302
303
  });
303
304
  if (this.dependsOn) {
305
+ this.validateDependsOnProperty();
304
306
  this.initFacetConditionManager(engine);
305
307
  }
306
308
  this.unsubscribe = this.facet.subscribe(() => this.updateState());
@@ -330,6 +332,26 @@ export default class QuanticCategoryFacet extends LightningElement {
330
332
  this.dispatchEvent(renderFacetEvent);
331
333
  }
332
334
 
335
+ validateDependsOnProperty() {
336
+ if (this.dependsOn) {
337
+ getBueno(this).then(() => {
338
+ const {parentFacetId, expectedValue} = this.dependsOn;
339
+ if (!Bueno.isString(parentFacetId)) {
340
+ console.error(
341
+ `The ${this.field} ${this.template.host.localName} requires dependsOn.parentFacetId to be a valid string.`
342
+ );
343
+ this.setInitializationError();
344
+ }
345
+ if (expectedValue && !Bueno.isString(expectedValue)) {
346
+ console.error(
347
+ `The ${this.field} ${this.template.host.localName} requires dependsOn.expectedValue to be a valid string.`
348
+ );
349
+ this.setInitializationError();
350
+ }
351
+ });
352
+ }
353
+ }
354
+
333
355
  initFacetConditionManager(engine) {
334
356
  this.categoryFacetConditionsManager =
335
357
  this.headless.buildFacetConditionsManager(engine, {
@@ -12,16 +12,21 @@ jest.mock('c/quanticUtils', () => ({
12
12
  DATEFACETS: 'dateFacets',
13
13
  },
14
14
  },
15
+ I18nUtils: {
16
+ format: jest.fn(),
17
+ },
15
18
  }));
16
19
  jest.mock('c/quanticHeadlessLoader');
17
20
 
18
21
  const selectors = {
19
22
  facetContent: '[data-test="facet-content"]',
23
+ componentError: 'c-quantic-component-error',
20
24
  };
21
25
 
22
26
  const exampleFacetId = 'example facet id';
27
+ const exampleField = 'exampleField';
23
28
  const defaultOptions = {
24
- field: 'example field',
29
+ field: exampleField,
25
30
  };
26
31
  const dateFacetControllerMock = {
27
32
  subscribe: jest.fn((callback) => callback()),
@@ -30,6 +35,8 @@ const dateFacetControllerMock = {
30
35
  values: [],
31
36
  },
32
37
  };
38
+ const parentFacetIdError = `The ${exampleField} c-quantic-date-facet requires dependsOn.parentFacetId to be a valid string.`;
39
+ const expectedValueError = `The ${exampleField} c-quantic-date-facet requires dependsOn.expectedValue to be a valid string.`;
33
40
 
34
41
  function createTestComponent(options = defaultOptions) {
35
42
  prepareHeadlessState();
@@ -79,6 +86,21 @@ const exampleEngine = {
79
86
  };
80
87
  let isInitialized = false;
81
88
 
89
+ function mockBueno() {
90
+ // @ts-ignore
91
+ mockHeadlessLoader.getBueno = () => {
92
+ // @ts-ignore
93
+ global.Bueno = {
94
+ isString: jest
95
+ .fn()
96
+ .mockImplementation(
97
+ (value) => Object.prototype.toString.call(value) === '[object String]'
98
+ ),
99
+ };
100
+ return new Promise((resolve) => resolve());
101
+ };
102
+ }
103
+
82
104
  function mockSuccessfulHeadlessInitialization() {
83
105
  // @ts-ignore
84
106
  mockHeadlessLoader.initializeWithHeadless = (element, _, initialize) => {
@@ -101,6 +123,7 @@ function cleanup() {
101
123
  describe('c-quantic-date-facet', () => {
102
124
  beforeAll(() => {
103
125
  mockSuccessfulHeadlessInitialization();
126
+ mockBueno();
104
127
  });
105
128
 
106
129
  afterEach(() => {
@@ -193,6 +216,89 @@ describe('c-quantic-date-facet', () => {
193
216
  });
194
217
  });
195
218
 
219
+ describe('validation of the dependsOn property', () => {
220
+ let consoleError;
221
+ beforeAll(() => {
222
+ consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
223
+ });
224
+ describe('when dependsOn.parentFacetId is not provided', () => {
225
+ it('should display the error component', async () => {
226
+ const invalidFacetDependency = {
227
+ expectedValue: 'txt',
228
+ };
229
+ const element = createTestComponent({
230
+ ...defaultOptions,
231
+ dependsOn: invalidFacetDependency,
232
+ });
233
+ await flushPromises();
234
+
235
+ const componentError = element.shadowRoot.querySelector(
236
+ selectors.componentError
237
+ );
238
+ const facetContent = element.shadowRoot.querySelector(
239
+ selectors.facetContent
240
+ );
241
+
242
+ expect(consoleError).toHaveBeenCalledTimes(1);
243
+ expect(consoleError).toHaveBeenCalledWith(parentFacetIdError);
244
+ expect(componentError).not.toBeNull();
245
+ expect(facetContent).toBeNull();
246
+ });
247
+ });
248
+
249
+ describe('when dependsOn.parentFacetId is not a string', () => {
250
+ it('should display the error component', async () => {
251
+ const invalidFacetDependency = {
252
+ parentFacetId: 1,
253
+ expectedValue: 'txt',
254
+ };
255
+ const element = createTestComponent({
256
+ ...defaultOptions,
257
+ dependsOn: invalidFacetDependency,
258
+ });
259
+ await flushPromises();
260
+
261
+ const componentError = element.shadowRoot.querySelector(
262
+ selectors.componentError
263
+ );
264
+ const facetContent = element.shadowRoot.querySelector(
265
+ selectors.facetContent
266
+ );
267
+
268
+ expect(consoleError).toHaveBeenCalledTimes(1);
269
+ expect(consoleError).toHaveBeenCalledWith(parentFacetIdError);
270
+ expect(componentError).not.toBeNull();
271
+ expect(facetContent).toBeNull();
272
+ });
273
+ });
274
+
275
+ describe('when dependsOn.expectedValue is not a string', () => {
276
+ it('should display the error component', async () => {
277
+ const invalidFacetDependency = {
278
+ parentFacetId: 'filetype',
279
+ expectedValue: 2,
280
+ };
281
+ const element = createTestComponent({
282
+ ...defaultOptions,
283
+ dependsOn: invalidFacetDependency,
284
+ });
285
+ await flushPromises();
286
+
287
+ const componentError = element.shadowRoot.querySelector(
288
+ selectors.componentError
289
+ );
290
+ const facetContent = element.shadowRoot.querySelector(
291
+ selectors.facetContent
292
+ );
293
+
294
+ expect(consoleError).toHaveBeenCalledTimes(1);
295
+ expect(consoleError).toHaveBeenCalledWith(expectedValueError);
296
+ expect(componentError).not.toBeNull();
297
+ expect(facetContent).toBeNull();
298
+ });
299
+ });
300
+ });
301
+
196
302
  describe('when the component is disconnected', () => {
197
303
  it('should make the condition manager stop watching the facet', async () => {
198
304
  const exampleFacetDependency = {
@@ -9,6 +9,7 @@ import {
9
9
  initializeWithHeadless,
10
10
  registerToStore,
11
11
  getHeadlessBundle,
12
+ getBueno,
12
13
  } from 'c/quanticHeadlessLoader';
13
14
  import {
14
15
  I18nUtils,
@@ -210,6 +211,7 @@ export default class QuanticDateFacet extends LightningElement {
210
211
  element: this.template.host,
211
212
  });
212
213
  if (this.dependsOn) {
214
+ this.validateDependsOnProperty();
213
215
  this.initFacetConditionManager(engine);
214
216
  }
215
217
  };
@@ -238,6 +240,26 @@ export default class QuanticDateFacet extends LightningElement {
238
240
  this.dispatchEvent(renderFacetEvent);
239
241
  }
240
242
 
243
+ validateDependsOnProperty() {
244
+ if (this.dependsOn) {
245
+ getBueno(this).then(() => {
246
+ const {parentFacetId, expectedValue} = this.dependsOn;
247
+ if (!Bueno.isString(parentFacetId)) {
248
+ console.error(
249
+ `The ${this.field} ${this.template.host.localName} requires dependsOn.parentFacetId to be a valid string.`
250
+ );
251
+ this.setInitializationError();
252
+ }
253
+ if (expectedValue && !Bueno.isString(expectedValue)) {
254
+ console.error(
255
+ `The ${this.field} ${this.template.host.localName} requires dependsOn.expectedValue to be a valid string.`
256
+ );
257
+ this.setInitializationError();
258
+ }
259
+ });
260
+ }
261
+ }
262
+
241
263
  initFacetConditionManager(engine) {
242
264
  this.dateFacetConditionsManager = this.headless.buildFacetConditionsManager(
243
265
  engine,
@@ -0,0 +1,199 @@
1
+ /* eslint-disable no-import-assign */
2
+ // @ts-ignore
3
+ import {createElement} from 'lwc';
4
+ import QuanticDidYouMean from '../quanticDidYouMean';
5
+ import * as mockHeadlessLoader from 'c/quanticHeadlessLoader';
6
+
7
+ jest.mock('c/quanticHeadlessLoader');
8
+
9
+ let isInitialized = false;
10
+
11
+ const exampleEngine = {
12
+ id: 'exampleEngineId',
13
+ };
14
+
15
+ const defaultQueryCorrectionMode = 'legacy';
16
+
17
+ const defaultOptions = {
18
+ engineId: exampleEngine.id,
19
+ disableQueryAutoCorrection: false,
20
+ queryCorrectionMode: defaultQueryCorrectionMode,
21
+ };
22
+
23
+ const mockDidYouMeanState = {
24
+ wasCorrectedTo: 'example corrected query',
25
+ originalQuery: 'example original query',
26
+ wasAutomaticallyCorrected: true,
27
+ queryCorrection: {
28
+ correctedQuery: 'example corrected query',
29
+ wordCorrected: 'example',
30
+ },
31
+ hasQueryCorrection: true,
32
+ };
33
+
34
+ const functionsMocks = {
35
+ buildDidYouMean: jest.fn(() => ({
36
+ state: mockDidYouMeanState,
37
+ subscribe: functionsMocks.subscribe,
38
+ })),
39
+ buildQueryTrigger: jest.fn(() => ({
40
+ state: {},
41
+ subscribe: functionsMocks.subscribe,
42
+ })),
43
+ applyCorrection: jest.fn(() => {}),
44
+ subscribe: jest.fn((cb) => {
45
+ cb();
46
+ return functionsMocks.unsubscribe;
47
+ }),
48
+ unsubscribe: jest.fn(() => {}),
49
+ };
50
+
51
+ function prepareHeadlessState() {
52
+ // @ts-ignore
53
+ mockHeadlessLoader.getHeadlessBundle = () => {
54
+ return {
55
+ buildDidYouMean: functionsMocks.buildDidYouMean,
56
+ buildQueryTrigger: functionsMocks.buildQueryTrigger,
57
+ };
58
+ };
59
+ }
60
+
61
+ function mockSuccessfulHeadlessInitialization() {
62
+ // @ts-ignore
63
+ mockHeadlessLoader.initializeWithHeadless = (element, _, initialize) => {
64
+ if (element instanceof QuanticDidYouMean && !isInitialized) {
65
+ isInitialized = true;
66
+ initialize(exampleEngine);
67
+ }
68
+ };
69
+ }
70
+
71
+ function createTestComponent(options = defaultOptions) {
72
+ prepareHeadlessState();
73
+ const element = createElement('c-quantic-did-you-mean', {
74
+ is: QuanticDidYouMean,
75
+ });
76
+
77
+ for (const [key, value] of Object.entries(options)) {
78
+ element[key] = value;
79
+ }
80
+
81
+ document.body.appendChild(element);
82
+ return element;
83
+ }
84
+
85
+ // Helper function to wait until the microtask queue is empty.
86
+ async function flushPromises() {
87
+ return Promise.resolve();
88
+ }
89
+
90
+ describe('c-quantic-did-you-mean', () => {
91
+ function cleanup() {
92
+ // The jsdom instance is shared across test cases in a single file so reset the DOM
93
+ while (document.body.firstChild) {
94
+ document.body.removeChild(document.body.firstChild);
95
+ }
96
+ jest.clearAllMocks();
97
+ isInitialized = false;
98
+ }
99
+
100
+ beforeEach(() => {
101
+ mockSuccessfulHeadlessInitialization();
102
+ });
103
+
104
+ afterEach(() => {
105
+ cleanup();
106
+ });
107
+
108
+ describe('controller initialization', () => {
109
+ it('should subscribe to the headless state changes', async () => {
110
+ createTestComponent();
111
+ await flushPromises();
112
+
113
+ expect(functionsMocks.buildDidYouMean).toHaveBeenCalled();
114
+ expect(functionsMocks.subscribe).toHaveBeenCalled();
115
+ });
116
+
117
+ describe('#disableQueryAutoCorrection property', () => {
118
+ describe('when disableQueryAutoCorrection is false (default)', () => {
119
+ it('should properly initialize the controller with automatic query correction enabled', async () => {
120
+ createTestComponent();
121
+ await flushPromises();
122
+
123
+ expect(functionsMocks.buildDidYouMean).toHaveBeenCalledTimes(1);
124
+ expect(functionsMocks.buildDidYouMean).toHaveBeenCalledWith(
125
+ exampleEngine,
126
+ expect.objectContaining({
127
+ options: expect.objectContaining({
128
+ automaticallyCorrectQuery: true,
129
+ queryCorrectionMode: defaultQueryCorrectionMode,
130
+ }),
131
+ })
132
+ );
133
+ });
134
+ });
135
+
136
+ describe('when disableQueryAutoCorrection is true', () => {
137
+ it('should properly initialize the controller with automatic query correction disabled', async () => {
138
+ createTestComponent({
139
+ ...defaultOptions,
140
+ disableQueryAutoCorrection: true,
141
+ });
142
+ await flushPromises();
143
+
144
+ expect(functionsMocks.buildDidYouMean).toHaveBeenCalledTimes(1);
145
+ expect(functionsMocks.buildDidYouMean).toHaveBeenCalledWith(
146
+ exampleEngine,
147
+ expect.objectContaining({
148
+ options: expect.objectContaining({
149
+ automaticallyCorrectQuery: false,
150
+ queryCorrectionMode: defaultQueryCorrectionMode,
151
+ }),
152
+ })
153
+ );
154
+ });
155
+ });
156
+ });
157
+
158
+ describe('#queryCorrectionMode property', () => {
159
+ describe('when queryCorrectionMode is `legacy` (default)', () => {
160
+ it('should properly initialize the controller with the query correction mode set to `legacy`', async () => {
161
+ createTestComponent();
162
+ await flushPromises();
163
+
164
+ expect(functionsMocks.buildDidYouMean).toHaveBeenCalledTimes(1);
165
+ expect(functionsMocks.buildDidYouMean).toHaveBeenCalledWith(
166
+ exampleEngine,
167
+ expect.objectContaining({
168
+ options: expect.objectContaining({
169
+ automaticallyCorrectQuery: true,
170
+ queryCorrectionMode: defaultQueryCorrectionMode,
171
+ }),
172
+ })
173
+ );
174
+ });
175
+ });
176
+
177
+ describe('when queryCorrectionMode is `next`', () => {
178
+ it('should properly initialize the controller with the query correction mode set to `next`', async () => {
179
+ createTestComponent({
180
+ ...defaultOptions,
181
+ queryCorrectionMode: 'next',
182
+ });
183
+ await flushPromises();
184
+
185
+ expect(functionsMocks.buildDidYouMean).toHaveBeenCalledTimes(1);
186
+ expect(functionsMocks.buildDidYouMean).toHaveBeenCalledWith(
187
+ exampleEngine,
188
+ expect.objectContaining({
189
+ options: expect.objectContaining({
190
+ automaticallyCorrectQuery: true,
191
+ queryCorrectionMode: 'next',
192
+ }),
193
+ })
194
+ );
195
+ });
196
+ });
197
+ });
198
+ });
199
+ });