@coveo/quantic 3.13.1 → 3.14.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 +11 -2
- package/force-app/main/default/labels/CustomLabels.labels-meta.xml +2 -2
- package/force-app/main/default/lwc/quanticBreadcrumbManager/__tests__/quanticBreadcrumbManager.test.js +6 -12
- package/force-app/main/default/lwc/quanticGeneratedAnswer/__tests__/quanticGeneratedAnswer.test.js +113 -6
- package/force-app/main/default/lwc/quanticGeneratedAnswer/quanticGeneratedAnswer.js +32 -5
- package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/generatedAnswer.html +1 -1
- package/force-app/main/default/lwc/quanticResultsPerPage/__tests__/quanticResultsPerPage.test.js +404 -0
- package/force-app/main/default/lwc/quanticResultsPerPage/e2e/fixture.ts +74 -0
- package/force-app/main/default/lwc/quanticResultsPerPage/e2e/pageObject.ts +85 -0
- package/force-app/main/default/lwc/quanticResultsPerPage/e2e/quanticResultsPerPage.e2e.ts +97 -0
- package/force-app/main/default/lwc/quanticResultsPerPage/quanticResultsPerPage.html +1 -1
- package/force-app/main/default/lwc/quanticResultsPerPage/quanticResultsPerPage.js +44 -9
- package/force-app/main/default/lwc/quanticSmartSnippet/e2e/data.ts +2 -11
- package/force-app/main/default/lwc/quanticSmartSnippet/e2e/fixture.ts +9 -5
- package/force-app/main/default/lwc/quanticSmartSnippet/e2e/pageObject.ts +1 -1
- package/force-app/main/default/lwc/quanticSmartSnippetAnswer/quanticSmartSnippetAnswer.html +1 -1
- package/force-app/main/default/lwc/quanticSmartSnippetSuggestions/__tests__/quanticSmartSnippetSuggestions.test.js +329 -0
- package/force-app/main/default/lwc/quanticSmartSnippetSuggestions/e2e/data.ts +31 -0
- package/force-app/main/default/lwc/quanticSmartSnippetSuggestions/e2e/fixture.ts +93 -0
- package/force-app/main/default/lwc/quanticSmartSnippetSuggestions/e2e/pageObject.ts +160 -0
- package/force-app/main/default/lwc/quanticSmartSnippetSuggestions/e2e/quanticSmartSnippetSuggestions.e2e.ts +127 -0
- package/force-app/main/default/lwc/quanticSmartSnippetSuggestions/quanticSmartSnippetSuggestions.html +2 -1
- package/force-app/main/default/staticresources/coveoheadless/case-assist/headless.js +6 -6
- package/force-app/main/default/staticresources/coveoheadless/definitions/api/service/insight/get-interface/get-interface-config-response.d.ts +4 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/commerce.index.d.ts +1 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/context/cart/headless-cart.ssr.d.ts +0 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/context/headless-context.ssr.d.ts +0 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/breadcrumb-manager/headless-core-breadcrumb-manager.ssr.d.ts +0 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/facets/generator/headless-commerce-facet-generator.ssr.d.ts +0 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/pagination/headless-core-commerce-pagination.ssr.d.ts +0 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.d.ts +0 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/sort/headless-core-commerce-sort.ssr.d.ts +0 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/summary/headless-core-summary.ssr.d.ts +0 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/field-suggestions/headless-field-suggestions-generator.ssr.d.ts +0 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/instant-products/headless-instant-products.ssr.d.ts +0 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/product-list/headless-product-list.ssr.d.ts +1 -3
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/product-view/headless-product-view.ssr.d.ts +0 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/recent-queries-list/headless-recent-queries-list.ssr.d.ts +0 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/recommendations/headless-recommendations.ssr.d.ts +0 -4
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/search/did-you-mean/headless-did-you-mean.ssr.d.ts +1 -3
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/search-box/headless-search-box.ssr.d.ts +0 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/standalone-search-box/headless-standalone-search-box.ssr.d.ts +0 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/triggers/headless-notify-trigger.ssr.d.ts +0 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/triggers/headless-query-trigger.ssr.d.ts +0 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/triggers/headless-redirection-trigger.ssr.d.ts +0 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/recent-queries/recent-queries-slice.d.ts +4 -2
- package/force-app/main/default/staticresources/coveoheadless/headless.js +8 -8
- package/force-app/main/default/staticresources/coveoheadless/insight/headless.js +7 -7
- package/force-app/main/default/staticresources/coveoheadless/recommendation/headless.js +6 -6
- package/package.json +10 -10
|
@@ -31,14 +31,28 @@ export default class QuanticResultsPerPage extends LightningElement {
|
|
|
31
31
|
* @type {string}
|
|
32
32
|
* @defaultValue `'10,25,50,100'`
|
|
33
33
|
*/
|
|
34
|
-
@api
|
|
34
|
+
@api
|
|
35
|
+
get choicesDisplayed() {
|
|
36
|
+
return this._choicesDisplayed;
|
|
37
|
+
}
|
|
38
|
+
set choicesDisplayed(value) {
|
|
39
|
+
if (this.validateChoicesDisplayed(value)) {
|
|
40
|
+
this._choicesDisplayed = value;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
35
43
|
/**
|
|
36
44
|
* The initial selection for the number of result per page. This should be part of the `choicesDisplayed` option. By default, this is set to the first value in `choicesDisplayed`.
|
|
37
45
|
* @api
|
|
38
46
|
* @type {number}
|
|
39
|
-
* @defaultValue `
|
|
47
|
+
* @defaultValue `the first value in 'choicesDisplayed'`
|
|
40
48
|
*/
|
|
41
|
-
@api
|
|
49
|
+
@api
|
|
50
|
+
get initialChoice() {
|
|
51
|
+
return this._initialChoice;
|
|
52
|
+
}
|
|
53
|
+
set initialChoice(value) {
|
|
54
|
+
this._initialChoice = value;
|
|
55
|
+
}
|
|
42
56
|
|
|
43
57
|
/** @type {boolean}*/
|
|
44
58
|
@track hasResults;
|
|
@@ -59,12 +73,21 @@ export default class QuanticResultsPerPage extends LightningElement {
|
|
|
59
73
|
headless;
|
|
60
74
|
/** @type {boolean} */
|
|
61
75
|
hasInitializationError = false;
|
|
76
|
+
/** @type {number} */
|
|
77
|
+
_initialChoice;
|
|
78
|
+
/** @type {string} */
|
|
79
|
+
_choicesDisplayed = '10,25,50,100';
|
|
62
80
|
|
|
63
81
|
labels = {
|
|
64
82
|
showNResultsPerPage,
|
|
65
83
|
};
|
|
66
84
|
|
|
67
85
|
connectedCallback() {
|
|
86
|
+
this.choices = this.parseChoicesDisplayed();
|
|
87
|
+
if (!this._initialChoice) {
|
|
88
|
+
this._initialChoice = this.choices[0];
|
|
89
|
+
}
|
|
90
|
+
this.validateInitialChoice();
|
|
68
91
|
registerComponentForInit(this, this.engineId);
|
|
69
92
|
}
|
|
70
93
|
|
|
@@ -77,8 +100,6 @@ export default class QuanticResultsPerPage extends LightningElement {
|
|
|
77
100
|
*/
|
|
78
101
|
initialize = (engine) => {
|
|
79
102
|
this.headless = getHeadlessBundle(this.engineId);
|
|
80
|
-
this.choices = this.parseChoicesDisplayed();
|
|
81
|
-
this.validateInitialChoice();
|
|
82
103
|
this.resultsPerPage = this.headless.buildResultsPerPage(engine, {
|
|
83
104
|
initialState: {
|
|
84
105
|
numberOfResults: Number(this.initialChoice) ?? this.choices[0],
|
|
@@ -105,19 +126,33 @@ export default class QuanticResultsPerPage extends LightningElement {
|
|
|
105
126
|
return this.choicesDisplayed.split(',').map((choice) => {
|
|
106
127
|
const parsedChoice = parseInt(choice, 10);
|
|
107
128
|
if (isNaN(parsedChoice)) {
|
|
108
|
-
|
|
129
|
+
this.setInitializationError();
|
|
130
|
+
}
|
|
131
|
+
return parsedChoice;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
validateChoicesDisplayed(value) {
|
|
136
|
+
let valid = true;
|
|
137
|
+
value.split(',').forEach((choice) => {
|
|
138
|
+
const parsedChoice = parseInt(choice, 10);
|
|
139
|
+
if (isNaN(parsedChoice)) {
|
|
140
|
+
console.error(
|
|
109
141
|
`The choice value "${choice}" from the "choicesDisplayed" option is not a number.`
|
|
110
142
|
);
|
|
143
|
+
this.setInitializationError();
|
|
144
|
+
valid = false;
|
|
111
145
|
}
|
|
112
|
-
return parsedChoice;
|
|
113
146
|
});
|
|
147
|
+
return valid;
|
|
114
148
|
}
|
|
115
149
|
|
|
116
150
|
validateInitialChoice() {
|
|
117
151
|
if (!this.choices.includes(Number(this.initialChoice))) {
|
|
118
|
-
|
|
119
|
-
`The "initialChoice" option value "${this.initialChoice}" is not included in the "choicesDisplayed" option "${this.choicesDisplayed}".`
|
|
152
|
+
console.error(
|
|
153
|
+
`The "initialChoice" option value "${this.initialChoice}" is not included in the "choicesDisplayed" option "${this.choicesDisplayed}". Defaulting to the first value of the choices.`
|
|
120
154
|
);
|
|
155
|
+
this._initialChoice = this.choices[0];
|
|
121
156
|
}
|
|
122
157
|
}
|
|
123
158
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {QuestionAnswerData} from '../../../../../../playwright/page-object/searchObjectWithSmartSnippet';
|
|
2
|
+
|
|
1
3
|
const exampleQuestion = 'Where was Gondor when the Westfold fell?';
|
|
2
4
|
const exampleAnswerSnippet = `<a href="#">Gondor</a> was on the brink of destruction when the Westfold fell. Gondor did not come to Rohan's aid when the Westfold fell because it was overwhelmed by its own dire circumstances and unable to spare resources or manpower. The alliance between the two kingdoms, forged by the Oath of Eorl, was based on a promise of mutual defense in times of extreme need. However, by the time of the War of the Ring, Gondor was facing unprecedented threats from Sauron, whose forces in Mordor were massing for a full-scale assault on Minas Tirith. The Steward of Gondor, Denethor II, was deeply concerned about the security of his own kingdom and could not afford to weaken its defenses to send aid across the vast distances that separated Gondor from the Westfold. Even if assistance had been feasible, the logistical challenge of moving troops through potentially hostile territory would have delayed their arrival, rendering their efforts ineffective. Moreover, the threat to Rohan came not only from Sauron's influence but also from the unexpected betrayal of Saruman, who, until then, had been perceived as an ally. His treachery, combined with the swiftness and brutality of his attacks, left Rohan vulnerable and isolated, as neither Rohan nor Gondor had anticipated such aggression from Isengard. While the alliance between the two realms remained strong in spirit, Gondor's immediate need to safeguard its own survival against the growing shadow of Mordor took precedence, leaving Rohan to fend for itself during the fall of the Westfold.`;
|
|
3
5
|
const exampleScore = 0.42;
|
|
@@ -5,17 +7,6 @@ const exampleContentIdKey = 'permanentid';
|
|
|
5
7
|
const mockPermanentId = '1234';
|
|
6
8
|
const exampleContentIdValue = mockPermanentId;
|
|
7
9
|
|
|
8
|
-
export type QuestionAnswerData = {
|
|
9
|
-
answerFound: boolean;
|
|
10
|
-
question: string;
|
|
11
|
-
answerSnippet: string;
|
|
12
|
-
documentId: {
|
|
13
|
-
contentIdKey: string;
|
|
14
|
-
contentIdValue: string;
|
|
15
|
-
};
|
|
16
|
-
score: number;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
10
|
const smartSnippetData: QuestionAnswerData = {
|
|
20
11
|
answerFound: true,
|
|
21
12
|
question: exampleQuestion,
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import {SmartSnippetObject} from './pageObject';
|
|
2
2
|
import {quanticBase} from '../../../../../../playwright/fixtures/baseFixture';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
SearchObjectWithSmartSnippet,
|
|
5
|
+
QuestionAnswerData,
|
|
6
|
+
} from '../../../../../../playwright/page-object/searchObjectWithSmartSnippet';
|
|
4
7
|
import {
|
|
5
8
|
searchRequestRegex,
|
|
6
9
|
insightSearchRequestRegex,
|
|
7
10
|
} from '../../../../../../playwright/utils/requests';
|
|
8
11
|
import {InsightSetupObject} from '../../../../../../playwright/page-object/insightSetupObject';
|
|
9
12
|
import {useCaseEnum} from '../../../../../../playwright/utils/useCase';
|
|
10
|
-
import type {QuestionAnswerData} from './data';
|
|
11
13
|
import smartSnippetData from './data';
|
|
12
14
|
|
|
13
15
|
const pageUrl = 's/quantic-smart-snippet';
|
|
@@ -19,7 +21,7 @@ interface SmartSnippetOptions {
|
|
|
19
21
|
type QuanticSmartSnippetE2ESearchFixtures = {
|
|
20
22
|
smartSnippetData: QuestionAnswerData;
|
|
21
23
|
smartSnippet: SmartSnippetObject;
|
|
22
|
-
search:
|
|
24
|
+
search: SearchObjectWithSmartSnippet;
|
|
23
25
|
options: Partial<SmartSnippetOptions>;
|
|
24
26
|
};
|
|
25
27
|
|
|
@@ -33,7 +35,7 @@ export const testSearch =
|
|
|
33
35
|
smartSnippetData,
|
|
34
36
|
options: {},
|
|
35
37
|
search: async ({page}, use) => {
|
|
36
|
-
await use(new
|
|
38
|
+
await use(new SearchObjectWithSmartSnippet(page, searchRequestRegex));
|
|
37
39
|
},
|
|
38
40
|
smartSnippet: async (
|
|
39
41
|
{page, options, configuration, search, smartSnippetData: data},
|
|
@@ -55,7 +57,9 @@ export const testInsight =
|
|
|
55
57
|
smartSnippetData,
|
|
56
58
|
options: {},
|
|
57
59
|
search: async ({page}, use) => {
|
|
58
|
-
await use(
|
|
60
|
+
await use(
|
|
61
|
+
new SearchObjectWithSmartSnippet(page, insightSearchRequestRegex)
|
|
62
|
+
);
|
|
59
63
|
},
|
|
60
64
|
insightSetup: async ({page}, use) => {
|
|
61
65
|
await use(new InsightSetupObject(page));
|
|
@@ -22,7 +22,7 @@ export class SmartSnippetObject {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
get smartSnippetAnwerInlineLink(): Locator {
|
|
25
|
-
return this.page.locator('
|
|
25
|
+
return this.page.locator('[data-testid="smart-snippet-answer"] > a');
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
get smartSnippetToggleButton(): Locator {
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/* eslint-disable no-import-assign */
|
|
2
|
+
import QuanticSmartSnippetSuggestions from '../quanticSmartSnippetSuggestions';
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
import {createElement} from 'lwc';
|
|
5
|
+
import * as mockHeadlessLoader from 'c/quanticHeadlessLoader';
|
|
6
|
+
|
|
7
|
+
jest.mock('c/quanticHeadlessLoader');
|
|
8
|
+
|
|
9
|
+
jest.mock(
|
|
10
|
+
'@salesforce/label/c.quantic_PeopleAlsoAsk',
|
|
11
|
+
() => ({default: 'People also ask'}),
|
|
12
|
+
{
|
|
13
|
+
virtual: true,
|
|
14
|
+
}
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
let isInitialized = false;
|
|
18
|
+
|
|
19
|
+
const exampleEngine = {
|
|
20
|
+
id: 'exampleEngineId',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const exampleUri = 'https://en.wikipedia.org/wiki/The_Lord_of_the_Rings';
|
|
24
|
+
|
|
25
|
+
const defaultOptions = {
|
|
26
|
+
engineId: exampleEngine.id,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const selectors = {
|
|
30
|
+
initializationError: 'c-quantic-component-error',
|
|
31
|
+
smartSnippetSuggestionsComponent:
|
|
32
|
+
'[data-testid="smart-snippet-suggestions-card"]',
|
|
33
|
+
smartSnippetSuggestions: '[data-testid="smart-snippet-suggestion"]',
|
|
34
|
+
smartSnippetSuggestionAnswers: 'c-quantic-smart-snippet-answer',
|
|
35
|
+
smartSnippetSuggestionSources: 'c-quantic-smart-snippet-source',
|
|
36
|
+
smartSnippetAccordionElement: 'lightning-accordion',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const mockSmartSnippetSource = {
|
|
40
|
+
title: 'The Lord of the Rings',
|
|
41
|
+
uri: exampleUri,
|
|
42
|
+
clickUri: exampleUri,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const mockSmartSnippetRelatedQuestions = [
|
|
46
|
+
{
|
|
47
|
+
question: 'Where is Gandalf?',
|
|
48
|
+
answer: 'Gandalf is in the Mines of Moria.',
|
|
49
|
+
documentId: {
|
|
50
|
+
contentIdKey: '2',
|
|
51
|
+
contentIdValue: '2',
|
|
52
|
+
},
|
|
53
|
+
questionAnswerId: '2',
|
|
54
|
+
expanded: false,
|
|
55
|
+
source: mockSmartSnippetSource,
|
|
56
|
+
score: 0.5,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
question: 'Legolas, what do you see?',
|
|
60
|
+
answer: "They're taking the hobbits to Isengard!",
|
|
61
|
+
documentId: {
|
|
62
|
+
contentIdKey: '3',
|
|
63
|
+
contentIdValue: '3',
|
|
64
|
+
},
|
|
65
|
+
questionAnswerId: '3',
|
|
66
|
+
expanded: false,
|
|
67
|
+
source: mockSmartSnippetSource,
|
|
68
|
+
score: 0.5,
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
const defaultSmartSnippetSuggestionsState = {
|
|
73
|
+
questions: mockSmartSnippetRelatedQuestions,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
let smartSnippetSuggestionsState = defaultSmartSnippetSuggestionsState;
|
|
77
|
+
|
|
78
|
+
const functionsMocks = {
|
|
79
|
+
buildSmartSnippetQuestionsList: jest.fn(() => ({
|
|
80
|
+
state: smartSnippetSuggestionsState,
|
|
81
|
+
subscribe: functionsMocks.smartSnippetSuggestionsStateSubscriber,
|
|
82
|
+
expand: functionsMocks.expand,
|
|
83
|
+
collapse: functionsMocks.collapse,
|
|
84
|
+
select: functionsMocks.select,
|
|
85
|
+
beginDelayedSelect: functionsMocks.beginDelayedSelect,
|
|
86
|
+
cancelPendingSelect: functionsMocks.cancelPendingSelect,
|
|
87
|
+
selectInlineLink: functionsMocks.selectInlineLink,
|
|
88
|
+
beginDelayedSelectInlineLink: functionsMocks.beginDelayedSelectInlineLink,
|
|
89
|
+
cancelPendingSelectInlineLink: functionsMocks.cancelPendingSelectInlineLink,
|
|
90
|
+
})),
|
|
91
|
+
smartSnippetSuggestionsStateSubscriber: jest.fn((cb) => {
|
|
92
|
+
cb();
|
|
93
|
+
return functionsMocks.smartSnippetSuggestionsStateUnsubscriber;
|
|
94
|
+
}),
|
|
95
|
+
smartSnippetSuggestionsStateUnsubscriber: jest.fn(),
|
|
96
|
+
expand: jest.fn(),
|
|
97
|
+
collapse: jest.fn(),
|
|
98
|
+
select: jest.fn(),
|
|
99
|
+
beginDelayedSelect: jest.fn(),
|
|
100
|
+
cancelPendingSelect: jest.fn(),
|
|
101
|
+
selectInlineLink: jest.fn(),
|
|
102
|
+
beginDelayedSelectInlineLink: jest.fn(),
|
|
103
|
+
cancelPendingSelectInlineLink: jest.fn(),
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
function createTestComponent(options = defaultOptions) {
|
|
107
|
+
const element = createElement('c-quantic-smart-snippet-suggestions', {
|
|
108
|
+
is: QuanticSmartSnippetSuggestions,
|
|
109
|
+
});
|
|
110
|
+
for (const [key, value] of Object.entries(options)) {
|
|
111
|
+
element[key] = value;
|
|
112
|
+
}
|
|
113
|
+
document.body.appendChild(element);
|
|
114
|
+
return element;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Helper function to wait until the microtask queue is empty.
|
|
118
|
+
function flushPromises() {
|
|
119
|
+
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function prepareHeadlessState() {
|
|
123
|
+
// @ts-ignore
|
|
124
|
+
mockHeadlessLoader.getHeadlessBundle = () => {
|
|
125
|
+
return {
|
|
126
|
+
buildSmartSnippetQuestionsList:
|
|
127
|
+
functionsMocks.buildSmartSnippetQuestionsList,
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function mockSuccessfulHeadlessInitialization() {
|
|
133
|
+
// @ts-ignore
|
|
134
|
+
mockHeadlessLoader.initializeWithHeadless = (element, _, initialize) => {
|
|
135
|
+
if (element instanceof QuanticSmartSnippetSuggestions && !isInitialized) {
|
|
136
|
+
isInitialized = true;
|
|
137
|
+
initialize(exampleEngine);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function mockErroneousHeadlessInitialization() {
|
|
143
|
+
// @ts-ignore
|
|
144
|
+
mockHeadlessLoader.initializeWithHeadless = (element) => {
|
|
145
|
+
if (element instanceof QuanticSmartSnippetSuggestions) {
|
|
146
|
+
element.setInitializationError();
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function cleanup() {
|
|
152
|
+
// The jsdom instance is shared across test cases in a single file so reset the DOM
|
|
153
|
+
while (document.body.firstChild) {
|
|
154
|
+
document.body.removeChild(document.body.firstChild);
|
|
155
|
+
}
|
|
156
|
+
jest.clearAllMocks();
|
|
157
|
+
isInitialized = false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
describe('c-quantic-smart-snippet-suggestions', () => {
|
|
161
|
+
afterEach(() => {
|
|
162
|
+
smartSnippetSuggestionsState = defaultSmartSnippetSuggestionsState;
|
|
163
|
+
cleanup();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('controller initialization', () => {
|
|
167
|
+
beforeEach(() => {
|
|
168
|
+
mockSuccessfulHeadlessInitialization();
|
|
169
|
+
prepareHeadlessState();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should build the smart snippet controller with the proper parameters and subscribe to its state change', async () => {
|
|
173
|
+
createTestComponent();
|
|
174
|
+
await flushPromises();
|
|
175
|
+
|
|
176
|
+
expect(
|
|
177
|
+
functionsMocks.buildSmartSnippetQuestionsList
|
|
178
|
+
).toHaveBeenCalledTimes(1);
|
|
179
|
+
expect(
|
|
180
|
+
functionsMocks.buildSmartSnippetQuestionsList
|
|
181
|
+
).toHaveBeenCalledWith(exampleEngine);
|
|
182
|
+
expect(
|
|
183
|
+
functionsMocks.smartSnippetSuggestionsStateSubscriber
|
|
184
|
+
).toHaveBeenCalledTimes(1);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe('when an initialization error occurs', () => {
|
|
189
|
+
beforeEach(() => {
|
|
190
|
+
mockErroneousHeadlessInitialization();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should display the initialization error component', async () => {
|
|
194
|
+
const element = createTestComponent();
|
|
195
|
+
await flushPromises();
|
|
196
|
+
|
|
197
|
+
const initializationError = element.shadowRoot.querySelector(
|
|
198
|
+
selectors.initializationError
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
expect(initializationError).not.toBeNull();
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('when the query does not return smart snippet suggestions', () => {
|
|
206
|
+
beforeEach(() => {
|
|
207
|
+
smartSnippetSuggestionsState = {
|
|
208
|
+
...defaultSmartSnippetSuggestionsState,
|
|
209
|
+
questions: [],
|
|
210
|
+
};
|
|
211
|
+
mockSuccessfulHeadlessInitialization();
|
|
212
|
+
prepareHeadlessState();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should not display the smart snippet suggestions component', async () => {
|
|
216
|
+
const element = createTestComponent();
|
|
217
|
+
await flushPromises();
|
|
218
|
+
|
|
219
|
+
const smartSnippetSuggestionsComponent = element.shadowRoot.querySelector(
|
|
220
|
+
selectors.smartSnippetSuggestionsComponent
|
|
221
|
+
);
|
|
222
|
+
expect(smartSnippetSuggestionsComponent).toBeNull();
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('when the query returns smart snippet suggestions', () => {
|
|
227
|
+
beforeEach(() => {
|
|
228
|
+
mockSuccessfulHeadlessInitialization();
|
|
229
|
+
prepareHeadlessState();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('should properly display the smart snippet suggestions', async () => {
|
|
233
|
+
const element = createTestComponent();
|
|
234
|
+
await flushPromises();
|
|
235
|
+
|
|
236
|
+
const smartSnippetSuggestionsComponent = element.shadowRoot.querySelector(
|
|
237
|
+
selectors.smartSnippetSuggestionsComponent
|
|
238
|
+
);
|
|
239
|
+
expect(smartSnippetSuggestionsComponent).not.toBeNull();
|
|
240
|
+
|
|
241
|
+
const smartSnippetSuggestions = element.shadowRoot.querySelectorAll(
|
|
242
|
+
selectors.smartSnippetSuggestions
|
|
243
|
+
);
|
|
244
|
+
const smartSnippetSuggestionAnswers = element.shadowRoot.querySelectorAll(
|
|
245
|
+
selectors.smartSnippetSuggestionAnswers
|
|
246
|
+
);
|
|
247
|
+
const smartSnippetSuggestionsSources =
|
|
248
|
+
element.shadowRoot.querySelectorAll(
|
|
249
|
+
selectors.smartSnippetSuggestionSources
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
mockSmartSnippetRelatedQuestions.forEach((question, index) => {
|
|
253
|
+
const suggestion = smartSnippetSuggestions[index];
|
|
254
|
+
const answerElement = smartSnippetSuggestionAnswers[index];
|
|
255
|
+
const sourceElement = smartSnippetSuggestionsSources[index];
|
|
256
|
+
|
|
257
|
+
expect(suggestion.label).toEqual(question.question);
|
|
258
|
+
expect(answerElement.answer).toEqual(question.answer);
|
|
259
|
+
expect(sourceElement.source).toEqual(question.source);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
describe('when clicking on the smart snippet suggestion', () => {
|
|
264
|
+
it('should call the expand and collapse methods from the smartSnippetSuggestions controller', async () => {
|
|
265
|
+
const element = createTestComponent();
|
|
266
|
+
await flushPromises();
|
|
267
|
+
|
|
268
|
+
const smartSnippetAccordionElement = element.shadowRoot.querySelector(
|
|
269
|
+
selectors.smartSnippetAccordionElement
|
|
270
|
+
);
|
|
271
|
+
expect(smartSnippetAccordionElement).not.toBeNull();
|
|
272
|
+
smartSnippetAccordionElement.activeSectionName = [
|
|
273
|
+
mockSmartSnippetRelatedQuestions[0].question,
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
const smartSnippetSuggestionsItems =
|
|
277
|
+
element.shadowRoot.querySelectorAll(
|
|
278
|
+
selectors.smartSnippetSuggestions
|
|
279
|
+
);
|
|
280
|
+
expect(smartSnippetSuggestionsItems[0]).not.toBeNull();
|
|
281
|
+
|
|
282
|
+
await smartSnippetSuggestionsItems[0].click();
|
|
283
|
+
await flushPromises();
|
|
284
|
+
|
|
285
|
+
expect(functionsMocks.expand).toHaveBeenCalledTimes(1);
|
|
286
|
+
expect(functionsMocks.expand).toHaveBeenCalledWith(
|
|
287
|
+
mockSmartSnippetRelatedQuestions[0].questionAnswerId
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
smartSnippetAccordionElement.activeSectionName = [];
|
|
291
|
+
await smartSnippetSuggestionsItems[0].click();
|
|
292
|
+
await flushPromises();
|
|
293
|
+
|
|
294
|
+
expect(functionsMocks.collapse).toHaveBeenCalledTimes(1);
|
|
295
|
+
expect(functionsMocks.collapse).toHaveBeenCalledWith(
|
|
296
|
+
mockSmartSnippetRelatedQuestions[0].questionAnswerId
|
|
297
|
+
);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('should pass the proper actions to the smart snippet answer and source', async () => {
|
|
302
|
+
const element = createTestComponent();
|
|
303
|
+
await flushPromises();
|
|
304
|
+
|
|
305
|
+
const smartSnippetAnswer = element.shadowRoot.querySelector(
|
|
306
|
+
selectors.smartSnippetSuggestionAnswers
|
|
307
|
+
);
|
|
308
|
+
const smartSnippetSource = element.shadowRoot.querySelector(
|
|
309
|
+
selectors.smartSnippetSuggestionSources
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
expect(smartSnippetAnswer).not.toBeNull();
|
|
313
|
+
expect(smartSnippetSource).not.toBeNull();
|
|
314
|
+
|
|
315
|
+
const expectedSmartSnippetSuggestionsActions = {
|
|
316
|
+
select: functionsMocks.select,
|
|
317
|
+
beginDelayedSelect: functionsMocks.beginDelayedSelect,
|
|
318
|
+
cancelPendingSelect: functionsMocks.cancelPendingSelect,
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
expect(Object.keys(smartSnippetAnswer.actions)).toEqual(
|
|
322
|
+
Object.keys(expectedSmartSnippetSuggestionsActions)
|
|
323
|
+
);
|
|
324
|
+
expect(Object.keys(smartSnippetSource.actions)).toEqual(
|
|
325
|
+
Object.keys(expectedSmartSnippetSuggestionsActions)
|
|
326
|
+
);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {RelatedQuestionsData} from '../../../../../../playwright/page-object/searchObjectWithSmartSnippet';
|
|
2
|
+
|
|
3
|
+
const exampleContentIdKey = 'permanentid';
|
|
4
|
+
const mockPermanentId = '1234';
|
|
5
|
+
const exampleContentIdValue = mockPermanentId;
|
|
6
|
+
|
|
7
|
+
const exampleRelatedQuestions = [
|
|
8
|
+
{
|
|
9
|
+
question: "What is the name of Gandalf's horse?",
|
|
10
|
+
answerSnippet:
|
|
11
|
+
'Is is Shadowfax, the lord of all horses. <a href="#">Click here to see the magnificent horse.</a>',
|
|
12
|
+
documentId: {
|
|
13
|
+
contentIdKey: exampleContentIdKey,
|
|
14
|
+
contentIdValue: exampleContentIdValue,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
question: 'What is the name of the sword that was broken?',
|
|
19
|
+
answerSnippet: 'It is Narsil, the sword of Elendil.',
|
|
20
|
+
documentId: {
|
|
21
|
+
contentIdKey: exampleContentIdKey,
|
|
22
|
+
contentIdValue: exampleContentIdValue,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const smartSnippetSuggestionsData: RelatedQuestionsData = {
|
|
28
|
+
relatedQuestions: exampleRelatedQuestions,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default smartSnippetSuggestionsData;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {SmartSnippetSuggestionsObject} from './pageObject';
|
|
2
|
+
import {quanticBase} from '../../../../../../playwright/fixtures/baseFixture';
|
|
3
|
+
import {
|
|
4
|
+
SearchObjectWithSmartSnippet,
|
|
5
|
+
RelatedQuestionsData,
|
|
6
|
+
} from '../../../../../../playwright/page-object/searchObjectWithSmartSnippet';
|
|
7
|
+
import {
|
|
8
|
+
searchRequestRegex,
|
|
9
|
+
insightSearchRequestRegex,
|
|
10
|
+
} from '../../../../../../playwright/utils/requests';
|
|
11
|
+
import {InsightSetupObject} from '../../../../../../playwright/page-object/insightSetupObject';
|
|
12
|
+
import {useCaseEnum} from '../../../../../../playwright/utils/useCase';
|
|
13
|
+
import smartSnippetSuggestionsData from './data';
|
|
14
|
+
|
|
15
|
+
const pageUrl = 's/quantic-smart-snippet-suggestions';
|
|
16
|
+
|
|
17
|
+
interface SmartSnippetSuggestionsOptions {}
|
|
18
|
+
|
|
19
|
+
type QuanticSmartSnippetSuggestionsE2ESearchFixtures = {
|
|
20
|
+
smartSnippetSuggestionsData: RelatedQuestionsData;
|
|
21
|
+
smartSnippetSuggestions: SmartSnippetSuggestionsObject;
|
|
22
|
+
search: SearchObjectWithSmartSnippet;
|
|
23
|
+
options: Partial<SmartSnippetSuggestionsOptions>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type QuanticSmartSnippetSuggestionsE2EInsightFixtures =
|
|
27
|
+
QuanticSmartSnippetSuggestionsE2ESearchFixtures & {
|
|
28
|
+
insightSetup: InsightSetupObject;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const testSearch =
|
|
32
|
+
quanticBase.extend<QuanticSmartSnippetSuggestionsE2ESearchFixtures>({
|
|
33
|
+
smartSnippetSuggestionsData,
|
|
34
|
+
options: {},
|
|
35
|
+
search: async ({page}, use) => {
|
|
36
|
+
await use(new SearchObjectWithSmartSnippet(page, searchRequestRegex));
|
|
37
|
+
},
|
|
38
|
+
smartSnippetSuggestions: async (
|
|
39
|
+
{page, options, configuration, search, smartSnippetSuggestionsData: data},
|
|
40
|
+
use
|
|
41
|
+
) => {
|
|
42
|
+
const smartSnippetSuggestionsObject = new SmartSnippetSuggestionsObject(
|
|
43
|
+
page
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
await page.goto(pageUrl);
|
|
47
|
+
await search.mockSearchWithSmartSnippetSuggestionsResponse(data);
|
|
48
|
+
|
|
49
|
+
configuration.configure(options);
|
|
50
|
+
await search.waitForSearchResponse();
|
|
51
|
+
await use(smartSnippetSuggestionsObject);
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export const testInsight =
|
|
56
|
+
quanticBase.extend<QuanticSmartSnippetSuggestionsE2EInsightFixtures>({
|
|
57
|
+
smartSnippetSuggestionsData,
|
|
58
|
+
options: {},
|
|
59
|
+
search: async ({page}, use) => {
|
|
60
|
+
await use(
|
|
61
|
+
new SearchObjectWithSmartSnippet(page, insightSearchRequestRegex)
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
insightSetup: async ({page}, use) => {
|
|
65
|
+
await use(new InsightSetupObject(page));
|
|
66
|
+
},
|
|
67
|
+
smartSnippetSuggestions: async (
|
|
68
|
+
{
|
|
69
|
+
page,
|
|
70
|
+
options,
|
|
71
|
+
search,
|
|
72
|
+
configuration,
|
|
73
|
+
insightSetup,
|
|
74
|
+
smartSnippetSuggestionsData: data,
|
|
75
|
+
},
|
|
76
|
+
use
|
|
77
|
+
) => {
|
|
78
|
+
const smartSnippetSuggestionsObject = new SmartSnippetSuggestionsObject(
|
|
79
|
+
page
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
await page.goto(pageUrl);
|
|
83
|
+
await search.mockSearchWithSmartSnippetSuggestionsResponse(data);
|
|
84
|
+
|
|
85
|
+
configuration.configure({...options, useCase: useCaseEnum.insight});
|
|
86
|
+
await insightSetup.waitForInsightInterfaceInitialization();
|
|
87
|
+
await search.performSearch();
|
|
88
|
+
await search.waitForSearchResponse();
|
|
89
|
+
await use(smartSnippetSuggestionsObject);
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
export {expect} from '@playwright/test';
|