@coveo/quantic 3.14.3 → 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.
- package/docs/out/quantic-docs.json +2 -2
- package/force-app/main/default/lwc/quanticCaseClassification/__tests__/quanticCaseClassification.test.js +819 -0
- package/force-app/main/default/lwc/quanticCaseClassification/e2e/data.ts +23 -0
- package/force-app/main/default/lwc/quanticCaseClassification/e2e/fixture.ts +51 -0
- package/force-app/main/default/lwc/quanticCaseClassification/e2e/pageObject.ts +152 -0
- package/force-app/main/default/lwc/quanticCaseClassification/e2e/quanticCaseClassification.e2e.ts +269 -0
- package/force-app/main/default/lwc/quanticCaseClassification/quanticCaseClassification.html +6 -6
- package/force-app/main/default/lwc/quanticFacetManager/__tests__/quanticFacetManager.test.js +178 -0
- package/force-app/main/default/lwc/quanticFacetManager/e2e/fixture.ts +63 -0
- package/force-app/main/default/lwc/quanticFacetManager/e2e/pageObject.ts +23 -0
- package/force-app/main/default/lwc/quanticFacetManager/e2e/quanticFacetManager.e2e.ts +82 -0
- package/force-app/main/default/lwc/quanticFacetManager/quanticFacetManager.js +1 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswer/__tests__/quanticGeneratedAnswer.test.js +54 -10
- package/force-app/main/default/lwc/quanticGeneratedAnswer/quanticGeneratedAnswer.js +19 -1
- package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/generatedAnswer.css +4 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/generatedAnswer.html +17 -0
- package/force-app/main/default/lwc/quanticTooltip/__tests__/quanticTooltip.test.js +1 -1
- package/force-app/main/default/staticresources/coveoheadless/case-assist/headless.js +2 -4
- package/force-app/main/default/staticresources/coveoheadless/definitions/commerce.index.d.ts +6 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/field-suggestions/headless-category-field-suggestions.d.ts +5 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/field-suggestions/headless-field-suggestions-generator.d.ts +9 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/filter-suggestions/headless-category-filter-suggestions.d.ts +87 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/filter-suggestions/headless-filter-suggestions-generator.d.ts +38 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/filter-suggestions/headless-filter-suggestions-generator.ssr.d.ts +16 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/filter-suggestions/headless-filter-suggestions.d.ts +88 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/ssr-commerce.index.d.ts +2 -2
- package/force-app/main/default/staticresources/coveoheadless/headless.js +3 -5
- package/force-app/main/default/staticresources/coveoheadless/insight/headless.js +3 -5
- package/force-app/main/default/staticresources/coveoheadless/recommendation/headless.js +2 -4
- package/package.json +2 -2
|
@@ -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);
|
package/force-app/main/default/lwc/quanticGeneratedAnswer/__tests__/quanticGeneratedAnswer.test.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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
|
-
|
|
732
|
-
|
|
733
|
-
|
|
750
|
+
const generatedAnswerCard = element.shadowRoot.querySelector(
|
|
751
|
+
selectors.generatedAnswerCard
|
|
752
|
+
);
|
|
753
|
+
expect(generatedAnswerCard).toBeNull();
|
|
734
754
|
|
|
735
|
-
|
|
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
|
|
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
|
*/
|
|
@@ -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('
|
|
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
|
|