@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.
Files changed (50) hide show
  1. package/docs/out/quantic-docs.json +11 -2
  2. package/force-app/main/default/labels/CustomLabels.labels-meta.xml +2 -2
  3. package/force-app/main/default/lwc/quanticBreadcrumbManager/__tests__/quanticBreadcrumbManager.test.js +6 -12
  4. package/force-app/main/default/lwc/quanticGeneratedAnswer/__tests__/quanticGeneratedAnswer.test.js +113 -6
  5. package/force-app/main/default/lwc/quanticGeneratedAnswer/quanticGeneratedAnswer.js +32 -5
  6. package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/generatedAnswer.html +1 -1
  7. package/force-app/main/default/lwc/quanticResultsPerPage/__tests__/quanticResultsPerPage.test.js +404 -0
  8. package/force-app/main/default/lwc/quanticResultsPerPage/e2e/fixture.ts +74 -0
  9. package/force-app/main/default/lwc/quanticResultsPerPage/e2e/pageObject.ts +85 -0
  10. package/force-app/main/default/lwc/quanticResultsPerPage/e2e/quanticResultsPerPage.e2e.ts +97 -0
  11. package/force-app/main/default/lwc/quanticResultsPerPage/quanticResultsPerPage.html +1 -1
  12. package/force-app/main/default/lwc/quanticResultsPerPage/quanticResultsPerPage.js +44 -9
  13. package/force-app/main/default/lwc/quanticSmartSnippet/e2e/data.ts +2 -11
  14. package/force-app/main/default/lwc/quanticSmartSnippet/e2e/fixture.ts +9 -5
  15. package/force-app/main/default/lwc/quanticSmartSnippet/e2e/pageObject.ts +1 -1
  16. package/force-app/main/default/lwc/quanticSmartSnippetAnswer/quanticSmartSnippetAnswer.html +1 -1
  17. package/force-app/main/default/lwc/quanticSmartSnippetSuggestions/__tests__/quanticSmartSnippetSuggestions.test.js +329 -0
  18. package/force-app/main/default/lwc/quanticSmartSnippetSuggestions/e2e/data.ts +31 -0
  19. package/force-app/main/default/lwc/quanticSmartSnippetSuggestions/e2e/fixture.ts +93 -0
  20. package/force-app/main/default/lwc/quanticSmartSnippetSuggestions/e2e/pageObject.ts +160 -0
  21. package/force-app/main/default/lwc/quanticSmartSnippetSuggestions/e2e/quanticSmartSnippetSuggestions.e2e.ts +127 -0
  22. package/force-app/main/default/lwc/quanticSmartSnippetSuggestions/quanticSmartSnippetSuggestions.html +2 -1
  23. package/force-app/main/default/staticresources/coveoheadless/case-assist/headless.js +6 -6
  24. package/force-app/main/default/staticresources/coveoheadless/definitions/api/service/insight/get-interface/get-interface-config-response.d.ts +4 -1
  25. package/force-app/main/default/staticresources/coveoheadless/definitions/commerce.index.d.ts +1 -0
  26. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/context/cart/headless-cart.ssr.d.ts +0 -2
  27. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/context/headless-context.ssr.d.ts +0 -2
  28. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/breadcrumb-manager/headless-core-breadcrumb-manager.ssr.d.ts +0 -2
  29. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/facets/generator/headless-commerce-facet-generator.ssr.d.ts +0 -2
  30. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/pagination/headless-core-commerce-pagination.ssr.d.ts +0 -2
  31. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.ssr.d.ts +0 -2
  32. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/sort/headless-core-commerce-sort.ssr.d.ts +0 -2
  33. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/summary/headless-core-summary.ssr.d.ts +0 -2
  34. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/field-suggestions/headless-field-suggestions-generator.ssr.d.ts +0 -2
  35. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/instant-products/headless-instant-products.ssr.d.ts +0 -2
  36. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/product-list/headless-product-list.ssr.d.ts +1 -3
  37. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/product-view/headless-product-view.ssr.d.ts +0 -1
  38. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/recent-queries-list/headless-recent-queries-list.ssr.d.ts +0 -2
  39. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/recommendations/headless-recommendations.ssr.d.ts +0 -4
  40. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/search/did-you-mean/headless-did-you-mean.ssr.d.ts +1 -3
  41. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/search-box/headless-search-box.ssr.d.ts +0 -2
  42. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/standalone-search-box/headless-standalone-search-box.ssr.d.ts +0 -2
  43. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/triggers/headless-notify-trigger.ssr.d.ts +0 -2
  44. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/triggers/headless-query-trigger.ssr.d.ts +0 -2
  45. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/triggers/headless-redirection-trigger.ssr.d.ts +0 -2
  46. package/force-app/main/default/staticresources/coveoheadless/definitions/features/recent-queries/recent-queries-slice.d.ts +4 -2
  47. package/force-app/main/default/staticresources/coveoheadless/headless.js +8 -8
  48. package/force-app/main/default/staticresources/coveoheadless/insight/headless.js +7 -7
  49. package/force-app/main/default/staticresources/coveoheadless/recommendation/headless.js +6 -6
  50. 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 choicesDisplayed = '10,25,50,100';
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 `10`
47
+ * @defaultValue `the first value in 'choicesDisplayed'`
40
48
  */
41
- @api initialChoice = 10;
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
- throw new Error(
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
- throw new Error(
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 {SearchObject} from '../../../../../../playwright/page-object/searchObject';
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: SearchObject;
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 SearchObject(page, searchRequestRegex));
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(new SearchObject(page, insightSearchRequestRegex));
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('.smart-snippet-answer a');
25
+ return this.page.locator('[data-testid="smart-snippet-answer"] > a');
26
26
  }
27
27
 
28
28
  get smartSnippetToggleButton(): Locator {
@@ -1,3 +1,3 @@
1
1
  <template>
2
- <div data-cy="smart-snippet-answer" class="smart-snippet-answer" lwc:dom="manual"></div>
2
+ <div data-testid="smart-snippet-answer" class="smart-snippet-answer" lwc:dom="manual"></div>
3
3
  </template>
@@ -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';