@coveo/quantic 3.14.2 → 3.15.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.
Files changed (56) hide show
  1. package/docs/out/quantic-docs.json +2 -2
  2. package/force-app/main/default/lwc/quanticCaseClassification/__tests__/quanticCaseClassification.test.js +819 -0
  3. package/force-app/main/default/lwc/quanticCaseClassification/e2e/data.ts +23 -0
  4. package/force-app/main/default/lwc/quanticCaseClassification/e2e/fixture.ts +51 -0
  5. package/force-app/main/default/lwc/quanticCaseClassification/e2e/pageObject.ts +152 -0
  6. package/force-app/main/default/lwc/quanticCaseClassification/e2e/quanticCaseClassification.e2e.ts +269 -0
  7. package/force-app/main/default/lwc/quanticCaseClassification/quanticCaseClassification.html +6 -6
  8. package/force-app/main/default/lwc/quanticFacetManager/__tests__/quanticFacetManager.test.js +178 -0
  9. package/force-app/main/default/lwc/quanticFacetManager/e2e/fixture.ts +63 -0
  10. package/force-app/main/default/lwc/quanticFacetManager/e2e/pageObject.ts +23 -0
  11. package/force-app/main/default/lwc/quanticFacetManager/e2e/quanticFacetManager.e2e.ts +82 -0
  12. package/force-app/main/default/lwc/quanticFacetManager/quanticFacetManager.js +1 -0
  13. package/force-app/main/default/lwc/quanticGeneratedAnswer/__tests__/quanticGeneratedAnswer.test.js +54 -10
  14. package/force-app/main/default/lwc/quanticGeneratedAnswer/quanticGeneratedAnswer.js +19 -1
  15. package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/generatedAnswer.css +4 -0
  16. package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/generatedAnswer.html +17 -0
  17. package/force-app/main/default/lwc/quanticTooltip/__tests__/quanticTooltip.test.js +1 -1
  18. package/force-app/main/default/staticresources/coveobueno/definitions/index.d.ts +7 -7
  19. package/force-app/main/default/staticresources/coveobueno/definitions/values/boolean-value.d.ts +2 -2
  20. package/force-app/main/default/staticresources/coveobueno/definitions/values/complex-value.d.ts +5 -5
  21. package/force-app/main/default/staticresources/coveobueno/definitions/values/enum-value.d.ts +2 -2
  22. package/force-app/main/default/staticresources/coveobueno/definitions/values/number-value.d.ts +2 -2
  23. package/force-app/main/default/staticresources/coveobueno/definitions/values/string-value.d.ts +2 -2
  24. package/force-app/main/default/staticresources/coveobueno/definitions/values/value.d.ts +1 -1
  25. package/force-app/main/default/staticresources/coveoheadless/case-assist/headless.js +7 -9
  26. package/force-app/main/default/staticresources/coveoheadless/definitions/api/commerce/common/product.d.ts +6 -0
  27. package/force-app/main/default/staticresources/coveoheadless/definitions/api/knowledge/stream-answer-api.d.ts +1 -1
  28. package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/core-engine.d.ts +9 -0
  29. package/force-app/main/default/staticresources/coveoheadless/definitions/commerce.index.d.ts +6 -1
  30. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/field-suggestions/headless-category-field-suggestions.d.ts +5 -1
  31. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/field-suggestions/headless-field-suggestions-generator.d.ts +9 -2
  32. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/filter-suggestions/headless-category-filter-suggestions.d.ts +87 -0
  33. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/filter-suggestions/headless-filter-suggestions-generator.d.ts +38 -0
  34. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/filter-suggestions/headless-filter-suggestions-generator.ssr.d.ts +16 -0
  35. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/filter-suggestions/headless-filter-suggestions.d.ts +88 -0
  36. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/core/facets/category-facet/headless-core-category-facet-options.d.ts +3 -1
  37. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/core/facets/facet/headless-core-facet-options.d.ts +3 -1
  38. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/core/facets/range-facet/date-facet/headless-core-date-filter.d.ts +3 -1
  39. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/core/facets/range-facet/date-facet/headless-date-facet-options.d.ts +3 -1
  40. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/core/facets/range-facet/numeric-facet/headless-core-numeric-filter.d.ts +3 -1
  41. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/core/facets/range-facet/numeric-facet/headless-numeric-facet-options.d.ts +3 -1
  42. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/facets/facet/headless-facet-options.d.ts +3 -1
  43. package/force-app/main/default/staticresources/coveoheadless/definitions/features/facets/category-facet-set/category-facet-set-actions.d.ts +3 -1
  44. package/force-app/main/default/staticresources/coveoheadless/definitions/features/facets/facet-api/request.d.ts +4 -1
  45. package/force-app/main/default/staticresources/coveoheadless/definitions/features/facets/facet-set/facet-set-actions.d.ts +3 -1
  46. package/force-app/main/default/staticresources/coveoheadless/definitions/features/facets/range-facets/date-facet-set/date-facet-actions.d.ts +3 -1
  47. package/force-app/main/default/staticresources/coveoheadless/definitions/features/facets/range-facets/numeric-facet-set/numeric-facet-actions.d.ts +3 -1
  48. package/force-app/main/default/staticresources/coveoheadless/definitions/ssr-commerce.index.d.ts +2 -2
  49. package/force-app/main/default/staticresources/coveoheadless/definitions/utils/fetch-event-source/fetch.d.ts +44 -0
  50. package/force-app/main/default/staticresources/coveoheadless/definitions/utils/fetch-event-source/index.d.ts +2 -0
  51. package/force-app/main/default/staticresources/coveoheadless/definitions/utils/fetch-event-source/parse.d.ts +36 -0
  52. package/force-app/main/default/staticresources/coveoheadless/headless.js +9 -11
  53. package/force-app/main/default/staticresources/coveoheadless/insight/headless.js +8 -10
  54. package/force-app/main/default/staticresources/coveoheadless/recommendation/headless.js +7 -9
  55. package/package.json +6 -6
  56. package/force-app/main/default/staticresources/coveoheadless/definitions/tsdoc-metadata.json +0 -11
@@ -0,0 +1,178 @@
1
+ /* eslint-disable no-import-assign */
2
+ import QuanticFacetManager from '../quanticFacetManager';
3
+ // @ts-ignore
4
+ import {createElement} from 'lwc';
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 defaultSearchStatusState = {
16
+ hasResults: true,
17
+ firstSearchExecuted: true,
18
+ };
19
+
20
+ let searchStatusState = defaultSearchStatusState;
21
+
22
+ const defaultOptions = {
23
+ engineId: exampleEngine.id,
24
+ };
25
+
26
+ const selectors = {
27
+ initializationError: 'c-quantic-component-error',
28
+ facetManagerHost: '.facet-manager__host',
29
+ };
30
+
31
+ const functionsMocks = {
32
+ buildFacetManager: jest.fn(() => ({
33
+ state: {},
34
+ subscribe: functionsMocks.facetManagerStateSubscriber,
35
+ })),
36
+ buildSearchStatus: jest.fn(() => ({
37
+ state: searchStatusState,
38
+ subscribe: functionsMocks.searchStatusStateSubscriber,
39
+ })),
40
+ facetManagerStateSubscriber: jest.fn((cb) => {
41
+ cb();
42
+ return functionsMocks.facetManagerStateUnsubscriber;
43
+ }),
44
+ searchStatusStateSubscriber: jest.fn((cb) => {
45
+ cb();
46
+ return functionsMocks.searchStatusStateUnsubscriber;
47
+ }),
48
+ facetManagerStateUnsubscriber: jest.fn(),
49
+ searchStatusStateUnsubscriber: jest.fn(),
50
+ };
51
+
52
+ function createTestComponent(options = defaultOptions) {
53
+ const element = createElement('c-quantic-facet-manager', {
54
+ is: QuanticFacetManager,
55
+ });
56
+ for (const [key, value] of Object.entries(options)) {
57
+ element[key] = value;
58
+ }
59
+ document.body.appendChild(element);
60
+ return element;
61
+ }
62
+
63
+ // Helper function to wait until the microtask queue is empty.
64
+ function flushPromises() {
65
+ return new Promise((resolve) => setTimeout(resolve, 0));
66
+ }
67
+
68
+ function prepareHeadlessState() {
69
+ // @ts-ignore
70
+ mockHeadlessLoader.getHeadlessBundle = () => {
71
+ return {
72
+ buildFacetManager: functionsMocks.buildFacetManager,
73
+ buildSearchStatus: functionsMocks.buildSearchStatus,
74
+ };
75
+ };
76
+ }
77
+
78
+ function mockSuccessfulHeadlessInitialization() {
79
+ // @ts-ignore
80
+ mockHeadlessLoader.initializeWithHeadless = (element, _, initialize) => {
81
+ if (element instanceof QuanticFacetManager && !isInitialized) {
82
+ isInitialized = true;
83
+ initialize(exampleEngine);
84
+ }
85
+ };
86
+ }
87
+
88
+ function mockErroneousHeadlessInitialization() {
89
+ // @ts-ignore
90
+ mockHeadlessLoader.initializeWithHeadless = (element) => {
91
+ if (element instanceof QuanticFacetManager) {
92
+ element.setInitializationError();
93
+ }
94
+ };
95
+ }
96
+
97
+ function cleanup() {
98
+ // The jsdom instance is shared across test cases in a single file so reset the DOM
99
+ while (document.body.firstChild) {
100
+ document.body.removeChild(document.body.firstChild);
101
+ }
102
+ jest.clearAllMocks();
103
+ isInitialized = false;
104
+ }
105
+
106
+ describe('c-quantic-facet-manager', () => {
107
+ afterEach(() => {
108
+ cleanup();
109
+ });
110
+
111
+ describe('controller initialization', () => {
112
+ beforeEach(() => {
113
+ mockSuccessfulHeadlessInitialization();
114
+ prepareHeadlessState();
115
+ });
116
+
117
+ it('should build the facet manager controller with the proper parameters and subscribe to its state', async () => {
118
+ createTestComponent();
119
+ await flushPromises();
120
+
121
+ expect(functionsMocks.buildFacetManager).toHaveBeenCalledTimes(1);
122
+ expect(functionsMocks.buildFacetManager).toHaveBeenCalledWith(
123
+ exampleEngine
124
+ );
125
+ expect(functionsMocks.facetManagerStateSubscriber).toHaveBeenCalledTimes(
126
+ 1
127
+ );
128
+ });
129
+
130
+ it('should build the search status controller with the proper parameters and subscribe to its state', async () => {
131
+ createTestComponent();
132
+ await flushPromises();
133
+
134
+ expect(functionsMocks.buildSearchStatus).toHaveBeenCalledTimes(1);
135
+ expect(functionsMocks.buildSearchStatus).toHaveBeenCalledWith(
136
+ exampleEngine
137
+ );
138
+ expect(functionsMocks.searchStatusStateSubscriber).toHaveBeenCalledTimes(
139
+ 1
140
+ );
141
+ });
142
+ });
143
+
144
+ describe('when an initialization error occurs', () => {
145
+ beforeEach(() => {
146
+ mockErroneousHeadlessInitialization();
147
+ });
148
+
149
+ it('should display the initialization error component', async () => {
150
+ const element = createTestComponent();
151
+ await flushPromises();
152
+
153
+ const initializationError = element.shadowRoot.querySelector(
154
+ selectors.initializationError
155
+ );
156
+
157
+ expect(initializationError).not.toBeNull();
158
+ });
159
+ });
160
+
161
+ describe('when the component is initialized', () => {
162
+ beforeEach(() => {
163
+ mockSuccessfulHeadlessInitialization();
164
+ prepareHeadlessState();
165
+ });
166
+
167
+ it('should properly render the facet manager component', async () => {
168
+ const element = createTestComponent();
169
+ await flushPromises();
170
+
171
+ const facetManager = element.shadowRoot.querySelector(
172
+ selectors.facetManagerHost
173
+ );
174
+
175
+ expect(facetManager).not.toBeNull();
176
+ });
177
+ });
178
+ });
@@ -0,0 +1,63 @@
1
+ import {FacetManagerObject} from './pageObject';
2
+ import {quanticBase} from '../../../../../../playwright/fixtures/baseFixture';
3
+ import {
4
+ searchRequestRegex,
5
+ insightSearchRequestRegex,
6
+ } from '../../../../../../playwright/utils/requests';
7
+ import {InsightSetupObject} from '../../../../../../playwright/page-object/insightSetupObject';
8
+ import {useCaseEnum} from '../../../../../../playwright/utils/useCase';
9
+ import {SearchObject} from '../../../../../../playwright/page-object/searchObject';
10
+
11
+ const pageUrl = 's/quantic-facet-manager';
12
+
13
+ interface FacetManagerOptions {}
14
+
15
+ type QuanticFacetManagerE2ESearchFixtures = {
16
+ facetManager: FacetManagerObject;
17
+ search: SearchObject;
18
+ options: Partial<FacetManagerOptions>;
19
+ };
20
+
21
+ type QuanticFacetManagerE2EInsightFixtures =
22
+ QuanticFacetManagerE2ESearchFixtures & {
23
+ insightSetup: InsightSetupObject;
24
+ };
25
+
26
+ export const testSearch =
27
+ quanticBase.extend<QuanticFacetManagerE2ESearchFixtures>({
28
+ options: {},
29
+ search: async ({page}, use) => {
30
+ await use(new SearchObject(page, searchRequestRegex));
31
+ },
32
+ facetManager: async ({page, options, configuration, search}, use) => {
33
+ await page.goto(pageUrl);
34
+ configuration.configure(options);
35
+ await search.waitForSearchResponse();
36
+
37
+ await use(new FacetManagerObject(page));
38
+ },
39
+ });
40
+
41
+ export const testInsight =
42
+ quanticBase.extend<QuanticFacetManagerE2EInsightFixtures>({
43
+ options: {},
44
+ search: async ({page}, use) => {
45
+ await use(new SearchObject(page, insightSearchRequestRegex));
46
+ },
47
+ insightSetup: async ({page}, use) => {
48
+ await use(new InsightSetupObject(page));
49
+ },
50
+ facetManager: async (
51
+ {page, options, search, configuration, insightSetup},
52
+ use
53
+ ) => {
54
+ await page.goto(pageUrl);
55
+ configuration.configure({...options, useCase: useCaseEnum.insight});
56
+ await insightSetup.waitForInsightInterfaceInitialization();
57
+ await search.performSearch();
58
+ await search.waitForSearchResponse();
59
+ await use(new FacetManagerObject(page));
60
+ },
61
+ });
62
+
63
+ export {expect} from '@playwright/test';
@@ -0,0 +1,23 @@
1
+ import type {Locator, Page} from '@playwright/test';
2
+
3
+ export class FacetManagerObject {
4
+ constructor(public page: Page) {
5
+ this.page = page;
6
+ }
7
+
8
+ get facetManager(): Locator {
9
+ return this.page.locator('c-quantic-facet-manager');
10
+ }
11
+
12
+ get facetManagerItems(): Locator {
13
+ return this.facetManager.getByTestId('facet-manager__item');
14
+ }
15
+
16
+ get itemTemplateSlot(): Locator {
17
+ return this.facetManager.locator('slot[name="itemTemplate"]');
18
+ }
19
+
20
+ getFacetManagerItemByIndex(index: number): Locator {
21
+ return this.facetManagerItems.nth(index);
22
+ }
23
+ }
@@ -0,0 +1,82 @@
1
+ import {testSearch, testInsight, expect} from './fixture';
2
+ import {useCaseTestCases} from '../../../../../../playwright/utils/useCase';
3
+
4
+ const fixtures = {
5
+ search: testSearch,
6
+ insight: testInsight,
7
+ };
8
+
9
+ const exampleFacetsOrder = ['objecttype', 'date', 'language'];
10
+
11
+ /**
12
+ * Filters out any facetId which contains a filter input "_input" because they are not displayed in the facet manager.
13
+ */
14
+ function filterInputFacets(facets: any): string[] {
15
+ return facets
16
+ .map((facet: any) => facet.facetId)
17
+ .filter((id: string) => !/_input$/.test(id));
18
+ }
19
+
20
+ useCaseTestCases.forEach((useCase) => {
21
+ let test = fixtures[useCase.value];
22
+
23
+ test.describe(`quantic facet manager ${useCase.label}`, () => {
24
+ test('should load facets in the same order as in the initial search response', async ({
25
+ facetManager,
26
+ search,
27
+ }) => {
28
+ const searchResponsePromise = search.waitForSearchResponse();
29
+ await search.performSearch();
30
+
31
+ const searchResponse = await searchResponsePromise;
32
+ const {facets} = await searchResponse.json();
33
+ expect(facets).not.toBeNull();
34
+
35
+ const filteredIds = filterInputFacets(facets);
36
+
37
+ filteredIds.forEach(async (facetId: any, index: number) => {
38
+ const facetManagerItem = facetManager.getFacetManagerItemByIndex(index);
39
+ expect(await facetManagerItem.getAttribute('data-facet-id')).toEqual(
40
+ facetId
41
+ );
42
+ });
43
+ });
44
+
45
+ test.describe('when the facets are reordered', () => {
46
+ test('should load facets in the reordered order', async ({
47
+ facetManager,
48
+ search,
49
+ }) => {
50
+ await search.mockSearchFacetOrder(exampleFacetsOrder);
51
+ const searchResponsePromise = search.waitForSearchResponse();
52
+ await search.performSearch();
53
+
54
+ const searchResponse = await searchResponsePromise;
55
+ const {facets} = await searchResponse.json();
56
+ expect(facets).not.toBeNull();
57
+
58
+ exampleFacetsOrder.forEach(async (facetId: string, index: number) => {
59
+ const facetManagerItem =
60
+ facetManager.getFacetManagerItemByIndex(index);
61
+ expect(await facetManagerItem.getAttribute('data-facet-id')).toEqual(
62
+ facetId
63
+ );
64
+ });
65
+ });
66
+ });
67
+
68
+ test.describe('when an itemTemplate slot is provided', () => {
69
+ test('should customize the element that wraps each facet according to the itemTemplate slot', async ({
70
+ facetManager,
71
+ }) => {
72
+ const expectedClass = 'slds-m-around_xx-large';
73
+ expect(facetManager.itemTemplateSlot).not.toBeNull();
74
+
75
+ const facetManagerItems = await facetManager.facetManagerItems.all();
76
+ facetManagerItems.forEach(async (item: any) => {
77
+ expect(await item.getAttribute('class')).toContain(expectedClass);
78
+ });
79
+ });
80
+ });
81
+ });
82
+ });
@@ -121,6 +121,7 @@ export default class QuanticFacetManager extends LightningElement {
121
121
  // @ts-ignore
122
122
  wrapper.setAttribute('data-facet-id', facet.facetId ?? facet.field);
123
123
  wrapper.classList.add('facet-manager__item');
124
+ wrapper.setAttribute('data-testid', 'facet-manager__item');
124
125
 
125
126
  wrapper.appendChild(facet);
126
127
  this.host.appendChild(wrapper);
@@ -7,6 +7,10 @@ import * as mockHeadlessLoader from 'c/quanticHeadlessLoader';
7
7
  let mockAnswerHeight = 250;
8
8
  let mockedConsoleWarn;
9
9
 
10
+ const exampleItemOne = document.createElement('div');
11
+ exampleItemOne.innerText = 'Custom error message';
12
+ const exampleAssignedElements = [exampleItemOne];
13
+
10
14
  jest.mock('c/quanticHeadlessLoader');
11
15
  jest.mock('c/quanticUtils', () => ({
12
16
  AriaLiveRegion: jest.fn(() => ({
@@ -35,7 +39,8 @@ const defaultOptions = {
35
39
  maxCollapsedHeight: 250,
36
40
  };
37
41
 
38
- function createTestComponent(options = defaultOptions) {
42
+ function createTestComponent(options = defaultOptions, assignedElements = []) {
43
+ mockSlotAssignedNodes(assignedElements);
39
44
  const element = createElement('c-quantic-generated-answer', {
40
45
  is: QuanticGeneratedAnswer,
41
46
  });
@@ -61,6 +66,8 @@ const selectors = {
61
66
  generatedAnswerCollapseToggle:
62
67
  '[data-testid="generated-answer__answer-toggle"]',
63
68
  generatedAnswerDisclaimer: '[data-testid="generated-answer__disclaimer"]',
69
+ generatedAnswerNoAnswerCard:
70
+ '[data-testid="generated-answer__no-answer-card"]',
64
71
  };
65
72
 
66
73
  const initialSearchStatusState = {
@@ -68,7 +75,7 @@ const initialSearchStatusState = {
68
75
  };
69
76
  let searchStatusState = initialSearchStatusState;
70
77
 
71
- const initialGeneratedAnswerState = {isVisible: true};
78
+ const initialGeneratedAnswerState = {isVisible: true, cannotAnswer: false};
72
79
  let generatedAnswerState = initialGeneratedAnswerState;
73
80
 
74
81
  const functionsMocks = {
@@ -94,6 +101,16 @@ const functionsMocks = {
94
101
  retry: jest.fn(),
95
102
  };
96
103
 
104
+ /**
105
+ * Mocks the return value of the assignedNodes method.
106
+ * @param {Array<Element>} assignedElements
107
+ */
108
+ function mockSlotAssignedNodes(assignedElements) {
109
+ HTMLSlotElement.prototype.assignedNodes = function () {
110
+ return assignedElements;
111
+ };
112
+ }
113
+
97
114
  // Helper function to wait until the microtask queue is empty.
98
115
  function flushPromises() {
99
116
  // eslint-disable-next-line @lwc/lwc/no-async-operation
@@ -706,7 +723,7 @@ describe('c-quantic-generated-answer', () => {
706
723
  });
707
724
  });
708
725
 
709
- describe('when the answer is empty', () => {
726
+ describe('when the answer cannot be generated after a query is executed', () => {
710
727
  const exampleEmptyAnswer = '';
711
728
  const exampleAnswerContentFormat = 'text/markdown';
712
729
  beforeEach(() => {
@@ -715,6 +732,7 @@ describe('c-quantic-generated-answer', () => {
715
732
  isStreaming: false,
716
733
  answer: exampleEmptyAnswer,
717
734
  answerContentFormat: exampleAnswerContentFormat,
735
+ cannotAnswer: true,
718
736
  };
719
737
  mockSuccessfulHeadlessInitialization();
720
738
  prepareHeadlessState();
@@ -724,15 +742,41 @@ describe('c-quantic-generated-answer', () => {
724
742
  generatedAnswerState = initialGeneratedAnswerState;
725
743
  });
726
744
 
727
- it('should not display the generated answer card', async () => {
728
- const element = createTestComponent();
729
- await flushPromises();
745
+ describe('when no custom error message slot is provided', () => {
746
+ it('should not display the generated answer card', async () => {
747
+ const element = createTestComponent();
748
+ await flushPromises();
730
749
 
731
- const generatedAnswerCard = element.shadowRoot.querySelector(
732
- selectors.generatedAnswerCard
733
- );
750
+ const generatedAnswerCard = element.shadowRoot.querySelector(
751
+ selectors.generatedAnswerCard
752
+ );
753
+ expect(generatedAnswerCard).toBeNull();
734
754
 
735
- expect(generatedAnswerCard).toBeNull();
755
+ const generatedAnswerCardNoAnswer = element.shadowRoot.querySelector(
756
+ selectors.generatedAnswerNoAnswerCard
757
+ );
758
+ expect(generatedAnswerCardNoAnswer).toBeNull();
759
+ });
760
+ });
761
+
762
+ describe('when a custom error message slot is provided', () => {
763
+ it('should properly display the no generated answer card', async () => {
764
+ const element = createTestComponent(
765
+ defaultOptions,
766
+ exampleAssignedElements
767
+ );
768
+ await flushPromises();
769
+
770
+ const generatedAnswerCard = element.shadowRoot.querySelector(
771
+ selectors.generatedAnswerCard
772
+ );
773
+ expect(generatedAnswerCard).toBeNull();
774
+
775
+ const generatedAnswerCardNoAnswer = element.shadowRoot.querySelector(
776
+ selectors.generatedAnswerNoAnswerCard
777
+ );
778
+ expect(generatedAnswerCardNoAnswer).not.toBeNull();
779
+ });
736
780
  });
737
781
  });
738
782
  });
@@ -52,9 +52,13 @@ const GENERATED_ANSWER_DATA_KEY = 'coveo-generated-answer-data';
52
52
 
53
53
  /**
54
54
  * The `QuanticGeneratedAnswer` component automatically generates an answer using Coveo machine learning models to answer the query executed by the user.
55
+ * This component includes a slot, "no-answer-message", which allows for rendering a custom message when no answer is generated.
55
56
  * @category Search
57
+ * @slot no-answer-message - Slot that allows the rendering of a custom message when no answer is generated.
56
58
  * @example
57
- * <c-quantic-generated-answer engine-id={engineId} with-toggle collapsible></c-quantic-generated-answer>
59
+ * <c-quantic-generated-answer engine-id={engineId} with-toggle collapsible>
60
+ * <div slot="no-answer-message">No answer was generated.</div>
61
+ * </c-quantic-generated-answer>
58
62
  */
59
63
  export default class QuanticGeneratedAnswer extends LightningElement {
60
64
  /**
@@ -573,6 +577,20 @@ export default class QuanticGeneratedAnswer extends LightningElement {
573
577
  return this.state?.expanded;
574
578
  }
575
579
 
580
+ /**
581
+ * Returns whether the component has a custom message to display when no answer is generated.
582
+ * @returns {boolean}
583
+ */
584
+ get hasCustomNoAnswerMessage() {
585
+ /** @type {HTMLSlotElement} */
586
+ const slot = this.template.querySelector('slot[name="no-answer-message"]');
587
+ return !!slot?.assignedNodes()?.length;
588
+ }
589
+
590
+ get shouldDisplayCustomNoAnswerMessage() {
591
+ return this.state?.cannotAnswer && this.hasCustomNoAnswerMessage;
592
+ }
593
+
576
594
  /**
577
595
  * Sets the component in the initialization error state.
578
596
  */
@@ -10,6 +10,10 @@
10
10
  position: relative;
11
11
  }
12
12
 
13
+ .generated-answer__content {
14
+ word-wrap: break-word;
15
+ }
16
+
13
17
  .generated-answer__footer {
14
18
  gap: 1rem;
15
19
  }
@@ -115,4 +115,21 @@
115
115
  </template>
116
116
  </div>
117
117
  </template>
118
+ <template lwc:if={shouldDisplayCustomNoAnswerMessage}>
119
+ <div data-testid="generated-answer__no-answer-card" class="slds-box">
120
+ <div class="generated-answer__card-header slds-grid">
121
+ <span class="generated-answer__badge slds-var-p-around_x-small slds-text-title_bold"
122
+ >{labels.generatedAnswerForYou}</span
123
+ >
124
+ </div>
125
+ <div class="generated-answer__content slds-var-m-top_medium">
126
+ <div class={generatedAnswerClass}>
127
+ <slot name="no-answer-message"></slot>
128
+ </div>
129
+ </div>
130
+ </div>
131
+ </template>
132
+ <div class="slds-hide">
133
+ <slot name="no-answer-message"></slot>
134
+ </div>
118
135
  </template>
@@ -91,7 +91,7 @@ describe('c-quantic-tooltip', () => {
91
91
  expect(tooltip.classList.contains('tooltip__content--visible')).toBe(false);
92
92
  });
93
93
 
94
- it('shouldnot display the tooltip when the slot content is empty', async () => {
94
+ it('should not display the tooltip when the slot content is empty', async () => {
95
95
  const element = createTestComponent(exampleOptions, []);
96
96
  await flushPromises();
97
97
 
@@ -1,7 +1,7 @@
1
- export * from './schema';
2
- export * from './values/value';
3
- export * from './values/number-value';
4
- export * from './values/boolean-value';
5
- export * from './values/string-value';
6
- export * from './values/complex-value';
7
- export * from './values/enum-value';
1
+ export * from './schema.js';
2
+ export * from './values/value.js';
3
+ export * from './values/number-value.js';
4
+ export * from './values/boolean-value.js';
5
+ export * from './values/string-value.js';
6
+ export * from './values/complex-value.js';
7
+ export * from './values/enum-value.js';
@@ -1,5 +1,5 @@
1
- import { SchemaValue } from '../schema';
2
- import { ValueConfig } from './value';
1
+ import { SchemaValue } from '../schema.js';
2
+ import { ValueConfig } from './value.js';
3
3
  type BooleanValueConfig = ValueConfig<boolean>;
4
4
  export declare class BooleanValue implements SchemaValue<boolean> {
5
5
  private value;
@@ -1,8 +1,8 @@
1
- import { SchemaDefinition, SchemaValue } from '../schema';
2
- import { BooleanValue } from './boolean-value';
3
- import { NumberValue } from './number-value';
4
- import { StringValue } from './string-value';
5
- import { PrimitivesValues, ValueConfig } from './value';
1
+ import { SchemaDefinition, SchemaValue } from '../schema.js';
2
+ import { BooleanValue } from './boolean-value.js';
3
+ import { NumberValue } from './number-value.js';
4
+ import { StringValue } from './string-value.js';
5
+ import { PrimitivesValues, ValueConfig } from './value.js';
6
6
  type RecordWithPrimitiveValues = Record<string, PrimitivesValues>;
7
7
  type ComplexRecord = Record<string, PrimitivesValues | RecordWithPrimitiveValues | ArrayValue>;
8
8
  type RecordValueConfig = {
@@ -1,5 +1,5 @@
1
- import { SchemaValue } from '../schema';
2
- import { ValueConfig } from './value';
1
+ import { SchemaValue } from '../schema.js';
2
+ import { ValueConfig } from './value.js';
3
3
  export interface EnumValueConfig<T> extends ValueConfig<T> {
4
4
  enum: Record<string | number, string | number>;
5
5
  }
@@ -1,5 +1,5 @@
1
- import { SchemaValue } from '../schema';
2
- import { ValueConfig } from './value';
1
+ import { SchemaValue } from '../schema.js';
2
+ import { ValueConfig } from './value.js';
3
3
  interface NumberValueConfig extends ValueConfig<number> {
4
4
  min?: number;
5
5
  max?: number;
@@ -1,5 +1,5 @@
1
- import { SchemaValue } from '../schema';
2
- import { ValueConfig } from './value';
1
+ import { SchemaValue } from '../schema.js';
2
+ import { ValueConfig } from './value.js';
3
3
  interface StringValueConfig<T extends string> extends ValueConfig<T> {
4
4
  emptyAllowed?: boolean;
5
5
  url?: boolean;
@@ -1,4 +1,4 @@
1
- import { SchemaValue } from '../schema';
1
+ import { SchemaValue } from '../schema.js';
2
2
  export type PrimitivesValues = boolean | number | string | object | undefined | null;
3
3
  export interface ValueConfig<T> {
4
4
  default?: (() => T) | T;