@coveo/quantic 3.14.0 → 3.14.1
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 +9 -0
- package/force-app/main/default/lwc/quanticFeedbackModal/success.html +1 -1
- package/force-app/main/default/lwc/quanticFeedbackModalQna/templates/success.html +1 -1
- package/force-app/main/default/lwc/quanticGeneratedAnswer/quanticGeneratedAnswer.js +1 -1
- package/force-app/main/default/lwc/quanticSmartSnippet/__tests__/quanticSmartSnippet.test.js +0 -16
- package/force-app/main/default/lwc/quanticSmartSnippet/e2e/pageObject.ts +8 -0
- package/force-app/main/default/lwc/quanticSmartSnippet/e2e/quanticSmartSnippet.e2e.ts +51 -1
- package/force-app/main/default/lwc/quanticSort/__tests__/quanticSort.test.js +218 -37
- package/force-app/main/default/lwc/quanticSort/e2e/fixture.ts +61 -0
- package/force-app/main/default/lwc/quanticSort/e2e/pageObject.ts +89 -0
- package/force-app/main/default/lwc/quanticSort/e2e/quanticSort.e2e.ts +91 -0
- package/force-app/main/default/lwc/quanticSort/quanticSort.js +7 -4
- package/force-app/main/default/staticresources/coveoheadless/case-assist/headless.js +1 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/facets/generator/headless-commerce-facet-generator.ssr.d.ts +2 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/facets/location/headless-commerce-location-facet.d.ts +11 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/facets/regular/headless-commerce-regular-facet.d.ts +8 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/pagination/headless-core-commerce-pagination.d.ts +3 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/pagination/headless-core-commerce-pagination.ssr.d.ts +2 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/ssr-commerce.index.d.ts +2 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/tsdoc-metadata.json +1 -1
- package/force-app/main/default/staticresources/coveoheadless/headless.js +1 -1
- package/force-app/main/default/staticresources/coveoheadless/insight/headless.js +1 -1
- package/force-app/main/default/staticresources/coveoheadless/recommendation/headless.js +1 -1
- package/force-app/main/default/staticresources/dompurify/purify.min.js +2 -2
- package/package.json +8 -8
|
@@ -1022,6 +1022,15 @@
|
|
|
1022
1022
|
"defaultValue": "{false}",
|
|
1023
1023
|
"type": "boolean"
|
|
1024
1024
|
},
|
|
1025
|
+
{
|
|
1026
|
+
"name": "answerConfigurationId",
|
|
1027
|
+
"attribute": "answer-configuration-id",
|
|
1028
|
+
"access": "@api",
|
|
1029
|
+
"description": "<p>The unique identifier of the answer configuration to use to generate the answer.</p>",
|
|
1030
|
+
"required": true,
|
|
1031
|
+
"defaultValue": "",
|
|
1032
|
+
"type": "string"
|
|
1033
|
+
},
|
|
1025
1034
|
{
|
|
1026
1035
|
"name": "maxCollapsedHeight",
|
|
1027
1036
|
"attribute": "max-collapsed-height",
|
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
</div>
|
|
10
10
|
</lightning-modal-body>
|
|
11
11
|
<lightning-modal-footer>
|
|
12
|
-
<lightning-button data-
|
|
12
|
+
<lightning-button data-testid="feedback-modal-footer__done" variant="brand" label={labels.done} onclick={closeModal}></lightning-button>
|
|
13
13
|
</lightning-modal-footer>
|
|
14
14
|
</template>
|
|
@@ -85,8 +85,8 @@ export default class QuanticGeneratedAnswer extends LightningElement {
|
|
|
85
85
|
*/
|
|
86
86
|
@api withToggle = false;
|
|
87
87
|
/**
|
|
88
|
-
* @internal
|
|
89
88
|
* The unique identifier of the answer configuration to use to generate the answer.
|
|
89
|
+
* @api
|
|
90
90
|
* @type {string}
|
|
91
91
|
*/
|
|
92
92
|
@api answerConfigurationId;
|
package/force-app/main/default/lwc/quanticSmartSnippet/__tests__/quanticSmartSnippet.test.js
CHANGED
|
@@ -337,21 +337,5 @@ describe('c-quantic-smart-snippet', () => {
|
|
|
337
337
|
});
|
|
338
338
|
});
|
|
339
339
|
});
|
|
340
|
-
|
|
341
|
-
describe('when interacting with the feedback modal', () => {
|
|
342
|
-
describe('when trying to open the feedback modal after executing the same query', () => {
|
|
343
|
-
// eslint-disable-next-line jest/no-disabled-tests, jest/expect-expect
|
|
344
|
-
it.skip('should not open the feedback modal', async () => {
|
|
345
|
-
// TODO: Implement this test - SFINT-5933
|
|
346
|
-
});
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
describe('when trying to open the feedback modal after executing a query that gave a new answer', () => {
|
|
350
|
-
// eslint-disable-next-line jest/no-disabled-tests, jest/expect-expect
|
|
351
|
-
it.skip('should open the feedback modal', async () => {
|
|
352
|
-
// TODO: Implement this test - SFINT-5933
|
|
353
|
-
});
|
|
354
|
-
});
|
|
355
|
-
});
|
|
356
340
|
});
|
|
357
341
|
});
|
|
@@ -45,6 +45,10 @@ export class SmartSnippetObject {
|
|
|
45
45
|
return this.page.getByTestId('feedback-modal-footer__cancel');
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
get feedbackModalDoneButton(): Locator {
|
|
49
|
+
return this.page.getByTestId('feedback-modal-footer__done');
|
|
50
|
+
}
|
|
51
|
+
|
|
48
52
|
get firstFeedbackOptionLabel(): Locator {
|
|
49
53
|
const labelFirstOption = "Didn't answer my question at all";
|
|
50
54
|
return this.page.getByText(labelFirstOption);
|
|
@@ -94,6 +98,10 @@ export class SmartSnippetObject {
|
|
|
94
98
|
await this.feedbackSubmitButton.click();
|
|
95
99
|
}
|
|
96
100
|
|
|
101
|
+
async clickFeedbackModalDoneButton(): Promise<void> {
|
|
102
|
+
await this.feedbackModalDoneButton.click();
|
|
103
|
+
}
|
|
104
|
+
|
|
97
105
|
async waitForExpandSmartSnippetUaAnalytics(): Promise<Request> {
|
|
98
106
|
return this.waitForSmartSnippetCustomUaAnalytics('expandSmartSnippet');
|
|
99
107
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {testSearch, testInsight} from './fixture';
|
|
1
|
+
import {testSearch, testInsight, expect} from './fixture';
|
|
2
2
|
import {useCaseTestCases} from '../../../../../../playwright/utils/useCase';
|
|
3
|
+
import smartSnippetData from './data';
|
|
3
4
|
|
|
4
5
|
const maximumSnippetHeight = 250;
|
|
5
6
|
|
|
@@ -113,6 +114,55 @@ useCaseTestCases.forEach((useCase) => {
|
|
|
113
114
|
});
|
|
114
115
|
});
|
|
115
116
|
});
|
|
117
|
+
|
|
118
|
+
test.describe('when trying to open the feedback modal after executing the same query', () => {
|
|
119
|
+
test('should not allow to open the feedback modal', async ({
|
|
120
|
+
smartSnippet,
|
|
121
|
+
search,
|
|
122
|
+
}) => {
|
|
123
|
+
await smartSnippet.clickDislikeButton();
|
|
124
|
+
await smartSnippet.clickExplainWhyButton();
|
|
125
|
+
await smartSnippet.selectFirstFeedbackOptionLabel();
|
|
126
|
+
await smartSnippet.clickFeedbackSubmitButton();
|
|
127
|
+
|
|
128
|
+
await smartSnippet.clickFeedbackModalDoneButton();
|
|
129
|
+
|
|
130
|
+
const searchResponsePromise = search.waitForSearchResponse();
|
|
131
|
+
await search.performSearch();
|
|
132
|
+
await searchResponsePromise;
|
|
133
|
+
|
|
134
|
+
expect(smartSnippet.dislikeButton).not.toBeNull();
|
|
135
|
+
await smartSnippet.clickDislikeButton();
|
|
136
|
+
expect(smartSnippet.explainWhyButton).not.toBeVisible();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test.describe('when trying to open the feedback modal after executing a query that gave a new answer', () => {
|
|
141
|
+
test('should allow to open the feedback modal', async ({
|
|
142
|
+
smartSnippet,
|
|
143
|
+
search,
|
|
144
|
+
}) => {
|
|
145
|
+
await smartSnippet.clickDislikeButton();
|
|
146
|
+
await smartSnippet.clickExplainWhyButton();
|
|
147
|
+
await smartSnippet.selectFirstFeedbackOptionLabel();
|
|
148
|
+
await smartSnippet.clickFeedbackSubmitButton();
|
|
149
|
+
|
|
150
|
+
await smartSnippet.clickFeedbackModalDoneButton();
|
|
151
|
+
|
|
152
|
+
await search.mockSearchWithSmartSnippetResponse({
|
|
153
|
+
...smartSnippetData,
|
|
154
|
+
question: 'new question',
|
|
155
|
+
answerSnippet: 'new answer',
|
|
156
|
+
});
|
|
157
|
+
const searchResponsePromise = search.waitForSearchResponse();
|
|
158
|
+
await search.performSearch();
|
|
159
|
+
await searchResponsePromise;
|
|
160
|
+
|
|
161
|
+
expect(smartSnippet.dislikeButton).not.toBeNull();
|
|
162
|
+
await smartSnippet.clickDislikeButton();
|
|
163
|
+
expect(smartSnippet.explainWhyButton).toBeVisible();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
116
166
|
});
|
|
117
167
|
});
|
|
118
168
|
});
|
|
@@ -5,6 +5,13 @@ import QuanticSort from 'c/quanticSort';
|
|
|
5
5
|
import {createElement} from 'lwc';
|
|
6
6
|
import * as mockHeadlessLoader from 'c/quanticHeadlessLoader';
|
|
7
7
|
|
|
8
|
+
const selectors = {
|
|
9
|
+
lightningCombobox: 'lightning-combobox',
|
|
10
|
+
componentError: 'c-quantic-component-error',
|
|
11
|
+
sortDropdown: '[data-testid="sort-dropdown"]',
|
|
12
|
+
initializationError: 'c-quantic-component-error',
|
|
13
|
+
};
|
|
14
|
+
|
|
8
15
|
const sortVariants = {
|
|
9
16
|
default: {
|
|
10
17
|
name: 'default',
|
|
@@ -20,25 +27,40 @@ const sortVariants = {
|
|
|
20
27
|
},
|
|
21
28
|
};
|
|
22
29
|
|
|
30
|
+
const sortByLabel = 'Sort By';
|
|
31
|
+
|
|
23
32
|
jest.mock('c/quanticHeadlessLoader');
|
|
24
|
-
jest.mock(
|
|
25
|
-
|
|
26
|
-
})
|
|
33
|
+
jest.mock(
|
|
34
|
+
'@salesforce/label/c.quantic_SortBy',
|
|
35
|
+
() => ({default: sortByLabel}),
|
|
36
|
+
{
|
|
37
|
+
virtual: true,
|
|
38
|
+
}
|
|
39
|
+
);
|
|
27
40
|
|
|
28
|
-
function mockBueno() {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
function mockBueno(shouldError = false) {
|
|
42
|
+
// @ts-ignore
|
|
43
|
+
mockHeadlessLoader.getBueno = () => {
|
|
44
|
+
// @ts-ignore
|
|
45
|
+
global.Bueno = {
|
|
46
|
+
isString: jest
|
|
47
|
+
.fn()
|
|
48
|
+
.mockImplementation(
|
|
49
|
+
(value) => Object.prototype.toString.call(value) === '[object String]'
|
|
50
|
+
),
|
|
51
|
+
StringValue: jest.fn(),
|
|
52
|
+
RecordValue: jest.fn(),
|
|
53
|
+
Schema: jest.fn(() => ({
|
|
54
|
+
validate: () => {
|
|
55
|
+
if (shouldError) {
|
|
56
|
+
throw new Error();
|
|
57
|
+
}
|
|
58
|
+
jest.fn();
|
|
59
|
+
},
|
|
60
|
+
})),
|
|
61
|
+
};
|
|
62
|
+
return new Promise((resolve) => resolve());
|
|
63
|
+
};
|
|
42
64
|
}
|
|
43
65
|
|
|
44
66
|
let isInitialized = false;
|
|
@@ -47,32 +69,35 @@ const exampleEngine = {
|
|
|
47
69
|
id: 'exampleEngineId',
|
|
48
70
|
};
|
|
49
71
|
|
|
50
|
-
const
|
|
72
|
+
const defaultSearchStatusState = {
|
|
51
73
|
hasResults: true,
|
|
52
74
|
};
|
|
53
|
-
|
|
54
|
-
const mockSearchStatus = {
|
|
55
|
-
state: mockSearchStatusState,
|
|
56
|
-
subscribe: jest.fn((callback) => {
|
|
57
|
-
callback();
|
|
58
|
-
return jest.fn();
|
|
59
|
-
}),
|
|
60
|
-
};
|
|
75
|
+
let searchStatusState = defaultSearchStatusState;
|
|
61
76
|
|
|
62
77
|
const functionsMocks = {
|
|
63
78
|
buildSort: jest.fn(() => ({
|
|
64
79
|
state: {},
|
|
65
|
-
subscribe: functionsMocks.
|
|
80
|
+
subscribe: functionsMocks.sortStateSubscriber,
|
|
81
|
+
sortBy: functionsMocks.sortBy,
|
|
82
|
+
})),
|
|
83
|
+
buildSearchStatus: jest.fn(() => ({
|
|
84
|
+
state: searchStatusState,
|
|
85
|
+
subscribe: functionsMocks.searchStatusStateSubscriber,
|
|
66
86
|
})),
|
|
67
87
|
buildCriterionExpression: jest.fn((criterion) => criterion),
|
|
68
88
|
buildRelevanceSortCriterion: jest.fn(() => 'relevance'),
|
|
69
89
|
buildDateSortCriterion: jest.fn(() => 'date'),
|
|
70
|
-
|
|
71
|
-
subscribe: jest.fn((cb) => {
|
|
90
|
+
sortStateSubscriber: jest.fn((cb) => {
|
|
72
91
|
cb();
|
|
73
|
-
return functionsMocks.
|
|
92
|
+
return functionsMocks.sortStateUnsubscriber;
|
|
74
93
|
}),
|
|
75
|
-
|
|
94
|
+
searchStatusStateSubscriber: jest.fn((cb) => {
|
|
95
|
+
cb();
|
|
96
|
+
return functionsMocks.searchStatusStateUnsubscriber;
|
|
97
|
+
}),
|
|
98
|
+
sortStateUnsubscriber: jest.fn(),
|
|
99
|
+
searchStatusStateUnsubscriber: jest.fn(),
|
|
100
|
+
sortBy: jest.fn(),
|
|
76
101
|
};
|
|
77
102
|
|
|
78
103
|
const defaultOptions = {
|
|
@@ -80,10 +105,19 @@ const defaultOptions = {
|
|
|
80
105
|
variant: 'default',
|
|
81
106
|
};
|
|
82
107
|
|
|
83
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Mocks the return value of the assignedNodes method.
|
|
110
|
+
* @param {Array<Element>} assignedElements
|
|
111
|
+
*/
|
|
112
|
+
function mockSlotAssignedNodes(assignedElements) {
|
|
113
|
+
HTMLSlotElement.prototype.assignedNodes = function () {
|
|
114
|
+
return assignedElements;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
84
117
|
|
|
85
|
-
function createTestComponent(options = defaultOptions) {
|
|
118
|
+
function createTestComponent(options = defaultOptions, assignedElements = []) {
|
|
86
119
|
prepareHeadlessState();
|
|
120
|
+
mockSlotAssignedNodes(assignedElements);
|
|
87
121
|
|
|
88
122
|
const element = createElement('c-quantic-sort', {
|
|
89
123
|
is: QuanticSort,
|
|
@@ -128,6 +162,15 @@ function mockSuccessfulHeadlessInitialization() {
|
|
|
128
162
|
};
|
|
129
163
|
}
|
|
130
164
|
|
|
165
|
+
function mockErroneousHeadlessInitialization() {
|
|
166
|
+
// @ts-ignore
|
|
167
|
+
mockHeadlessLoader.initializeWithHeadless = (element) => {
|
|
168
|
+
if (element instanceof QuanticSort) {
|
|
169
|
+
element.setInitializationError();
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
131
174
|
function cleanup() {
|
|
132
175
|
// The jsdom instance is shared across test cases in a single file so reset the DOM
|
|
133
176
|
while (document.body.firstChild) {
|
|
@@ -145,14 +188,152 @@ describe('c-quantic-sort', () => {
|
|
|
145
188
|
|
|
146
189
|
afterEach(() => {
|
|
147
190
|
cleanup();
|
|
191
|
+
searchStatusState = defaultSearchStatusState;
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('when an initialization error occurs', () => {
|
|
195
|
+
beforeEach(() => {
|
|
196
|
+
mockErroneousHeadlessInitialization();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should display the initialization error component', async () => {
|
|
200
|
+
const element = createTestComponent();
|
|
201
|
+
await flushPromises();
|
|
202
|
+
|
|
203
|
+
const initializationError = element.shadowRoot.querySelector(
|
|
204
|
+
selectors.initializationError
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
expect(initializationError).not.toBeNull();
|
|
208
|
+
});
|
|
148
209
|
});
|
|
149
210
|
|
|
150
211
|
describe('controller initialization', () => {
|
|
151
|
-
it('should subscribe to the headless state changes', async () => {
|
|
212
|
+
it('should subscribe to the headless sort and search status state changes', async () => {
|
|
152
213
|
createTestComponent();
|
|
153
214
|
await flushPromises();
|
|
154
215
|
|
|
155
|
-
expect(functionsMocks.
|
|
216
|
+
expect(functionsMocks.sortStateSubscriber).toHaveBeenCalledTimes(1);
|
|
217
|
+
expect(functionsMocks.searchStatusStateSubscriber).toHaveBeenCalledTimes(
|
|
218
|
+
1
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('when no results are found', () => {
|
|
224
|
+
beforeAll(() => {
|
|
225
|
+
searchStatusState = {...defaultSearchStatusState, hasResults: false};
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('should not display the sort dropdown', async () => {
|
|
229
|
+
const element = createTestComponent();
|
|
230
|
+
await flushPromises();
|
|
231
|
+
|
|
232
|
+
const sortDropdown = element.shadowRoot.querySelector(
|
|
233
|
+
selectors.sortDropdown
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
expect(sortDropdown).toBeNull();
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('when a sort option is selected', () => {
|
|
241
|
+
it('should call the sortBy method of the sort controller', async () => {
|
|
242
|
+
const element = createTestComponent();
|
|
243
|
+
await flushPromises();
|
|
244
|
+
|
|
245
|
+
const lightningCombobox = element.shadowRoot.querySelector(
|
|
246
|
+
selectors.lightningCombobox
|
|
247
|
+
);
|
|
248
|
+
const exampleDefaultSortOptionValue = 'relevance';
|
|
249
|
+
expect(lightningCombobox).not.toBeNull();
|
|
250
|
+
|
|
251
|
+
lightningCombobox.dispatchEvent(
|
|
252
|
+
new CustomEvent('change', {
|
|
253
|
+
detail: {value: exampleDefaultSortOptionValue},
|
|
254
|
+
})
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
expect(functionsMocks.sortBy).toHaveBeenCalledTimes(1);
|
|
258
|
+
expect(functionsMocks.sortBy).toHaveBeenCalledWith(
|
|
259
|
+
exampleDefaultSortOptionValue
|
|
260
|
+
);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe('when custom sort options are passed', () => {
|
|
265
|
+
const exampleSlot = {
|
|
266
|
+
value: 'example value',
|
|
267
|
+
label: 'example label',
|
|
268
|
+
criterion: {
|
|
269
|
+
by: 'example field',
|
|
270
|
+
order: 'example order',
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
const exampleAssignedElements = [exampleSlot];
|
|
274
|
+
|
|
275
|
+
it('should build the controller with the correct sort option and display the custom sort options', async () => {
|
|
276
|
+
const element = createTestComponent(
|
|
277
|
+
defaultOptions,
|
|
278
|
+
exampleAssignedElements
|
|
279
|
+
);
|
|
280
|
+
await flushPromises();
|
|
281
|
+
|
|
282
|
+
expect(functionsMocks.buildSort).toHaveBeenCalledTimes(1);
|
|
283
|
+
expect(functionsMocks.buildSort).toHaveBeenCalledWith(exampleEngine, {
|
|
284
|
+
initialState: {
|
|
285
|
+
criterion: {
|
|
286
|
+
by: 'example field',
|
|
287
|
+
order: 'example order',
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
const lightningCombobox = element.shadowRoot.querySelector(
|
|
292
|
+
selectors.lightningCombobox
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
expect(lightningCombobox.options).toEqual([exampleSlot]);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
describe('when invalid sort options are passed', () => {
|
|
300
|
+
beforeEach(() => {
|
|
301
|
+
mockBueno(true);
|
|
302
|
+
jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const invalidExampleSlot = {
|
|
306
|
+
value: 'example value',
|
|
307
|
+
label: '',
|
|
308
|
+
criterion: {
|
|
309
|
+
by: 'example field',
|
|
310
|
+
order: 'example order',
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
const exampleAssignedElements = [invalidExampleSlot];
|
|
314
|
+
|
|
315
|
+
it('should display the component error', async () => {
|
|
316
|
+
const element = createTestComponent(
|
|
317
|
+
defaultOptions,
|
|
318
|
+
exampleAssignedElements
|
|
319
|
+
);
|
|
320
|
+
await flushPromises();
|
|
321
|
+
|
|
322
|
+
expect(functionsMocks.buildSort).toHaveBeenCalledTimes(1);
|
|
323
|
+
expect(functionsMocks.buildSort).toHaveBeenCalledWith(exampleEngine, {
|
|
324
|
+
initialState: {
|
|
325
|
+
criterion: {
|
|
326
|
+
by: 'example field',
|
|
327
|
+
order: 'example order',
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
const componentError = element.shadowRoot.querySelector(
|
|
332
|
+
selectors.componentError
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
expect(componentError).not.toBeNull();
|
|
336
|
+
expect(console.error).toHaveBeenCalledTimes(1);
|
|
156
337
|
});
|
|
157
338
|
});
|
|
158
339
|
|
|
@@ -187,7 +368,7 @@ describe('c-quantic-sort', () => {
|
|
|
187
368
|
|
|
188
369
|
const sortLabel = element.shadowRoot.querySelector(variant.labelSelector);
|
|
189
370
|
|
|
190
|
-
expect(sortLabel.value).toBe(
|
|
371
|
+
expect(sortLabel.value).toBe(sortByLabel);
|
|
191
372
|
});
|
|
192
373
|
});
|
|
193
374
|
|
|
@@ -228,7 +409,7 @@ describe('c-quantic-sort', () => {
|
|
|
228
409
|
|
|
229
410
|
const sortLabel = element.shadowRoot.querySelector(variant.labelSelector);
|
|
230
411
|
|
|
231
|
-
expect(sortLabel.textContent).toBe(
|
|
412
|
+
expect(sortLabel.textContent).toBe(sortByLabel);
|
|
232
413
|
expect(sortLabel.classList).toContain('slds-text-heading_small');
|
|
233
414
|
});
|
|
234
415
|
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {SortObject} from './pageObject';
|
|
2
|
+
import {quanticBase} from '../../../../../../playwright/fixtures/baseFixture';
|
|
3
|
+
import {SearchObject} from '../../../../../../playwright/page-object/searchObject';
|
|
4
|
+
import {
|
|
5
|
+
searchRequestRegex,
|
|
6
|
+
insightSearchRequestRegex,
|
|
7
|
+
} from '../../../../../../playwright/utils/requests';
|
|
8
|
+
import {InsightSetupObject} from '../../../../../../playwright/page-object/insightSetupObject';
|
|
9
|
+
import {useCaseEnum} from '../../../../../../playwright/utils/useCase';
|
|
10
|
+
|
|
11
|
+
const sortUrl = 's/quantic-sort';
|
|
12
|
+
|
|
13
|
+
interface SortOptions {}
|
|
14
|
+
|
|
15
|
+
type QuanticSortE2EFixtures = {
|
|
16
|
+
sort: SortObject;
|
|
17
|
+
search: SearchObject;
|
|
18
|
+
options: Partial<SortOptions>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type QuanticSortE2ESearchFixtures = QuanticSortE2EFixtures & {
|
|
22
|
+
urlHash: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type QuanticSortE2EInsightFixtures = QuanticSortE2EFixtures & {
|
|
26
|
+
insightSetup: InsightSetupObject;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const testSearch = quanticBase.extend<QuanticSortE2ESearchFixtures>({
|
|
30
|
+
options: {},
|
|
31
|
+
urlHash: '',
|
|
32
|
+
search: async ({page}, use) => {
|
|
33
|
+
await use(new SearchObject(page, searchRequestRegex));
|
|
34
|
+
},
|
|
35
|
+
sort: async ({page, options, configuration, search, urlHash}, use) => {
|
|
36
|
+
await page.goto(urlHash ? `${sortUrl}#${urlHash}` : sortUrl);
|
|
37
|
+
configuration.configure(options);
|
|
38
|
+
await search.waitForSearchResponse();
|
|
39
|
+
await use(new SortObject(page));
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export const testInsight = quanticBase.extend<QuanticSortE2EInsightFixtures>({
|
|
44
|
+
options: {},
|
|
45
|
+
search: async ({page}, use) => {
|
|
46
|
+
await use(new SearchObject(page, insightSearchRequestRegex));
|
|
47
|
+
},
|
|
48
|
+
insightSetup: async ({page}, use) => {
|
|
49
|
+
await use(new InsightSetupObject(page));
|
|
50
|
+
},
|
|
51
|
+
sort: async ({page, options, search, configuration, insightSetup}, use) => {
|
|
52
|
+
await page.goto(sortUrl);
|
|
53
|
+
configuration.configure({...options, useCase: useCaseEnum.insight});
|
|
54
|
+
await insightSetup.waitForInsightInterfaceInitialization();
|
|
55
|
+
await search.performSearch();
|
|
56
|
+
await search.waitForSearchResponse();
|
|
57
|
+
await use(new SortObject(page));
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export {expect} from '@playwright/test';
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type {Locator, Page, Request} from '@playwright/test';
|
|
2
|
+
import {isUaSearchEvent} from '../../../../../../playwright/utils/requests';
|
|
3
|
+
|
|
4
|
+
export class SortObject {
|
|
5
|
+
constructor(public page: Page) {
|
|
6
|
+
this.page = page;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
get sortDropDown(): Locator {
|
|
10
|
+
return this.page.getByRole('combobox', {name: 'Sort by'});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get sortPreviewButton(): Locator {
|
|
14
|
+
return this.page.getByRole('button', {name: 'Preview'});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
sortButton(buttonName: string): Locator {
|
|
18
|
+
return this.page.getByRole('option', {name: buttonName});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async clickSortDropDown(): Promise<void> {
|
|
22
|
+
await this.sortDropDown.click();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async focusSortDropDown(): Promise<void> {
|
|
26
|
+
await this.sortPreviewButton.click();
|
|
27
|
+
await this.page.keyboard.press('Tab');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async clickSortButton(buttonName: string): Promise<void> {
|
|
31
|
+
await this.sortButton(buttonName).click();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async openSortDropdownUsingKeyboardEnter(useEnter = true): Promise<void> {
|
|
35
|
+
if (useEnter) {
|
|
36
|
+
await this.page.keyboard.press('Enter');
|
|
37
|
+
} else {
|
|
38
|
+
await this.page.keyboard.press('Space');
|
|
39
|
+
}
|
|
40
|
+
await this.sortButton('Oldest').isVisible();
|
|
41
|
+
await this.page.waitForTimeout(500);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async waitForSortUaAnalytics(eventValue: any): Promise<Request> {
|
|
45
|
+
const uaRequest = this.page.waitForRequest((request) => {
|
|
46
|
+
if (isUaSearchEvent(request)) {
|
|
47
|
+
const requestBody = request.postDataJSON();
|
|
48
|
+
const expectedFields = {
|
|
49
|
+
actionCause: 'resultsSort',
|
|
50
|
+
customData: {
|
|
51
|
+
resultsSortBy: eventValue,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const validateObject = (obj: any, expectedResult: any): boolean =>
|
|
56
|
+
Object.entries(expectedResult).every(([key, value]) =>
|
|
57
|
+
value && typeof value === 'object'
|
|
58
|
+
? validateObject(obj?.[key], value)
|
|
59
|
+
: obj?.[key] === value
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return validateObject(requestBody, expectedFields);
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
});
|
|
66
|
+
return uaRequest;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async allSortLabelOptions() {
|
|
70
|
+
const options = await this.page
|
|
71
|
+
.locator('div[part="dropdown overlay"]')
|
|
72
|
+
.allInnerTexts();
|
|
73
|
+
const arrSort = options[0].split('\n');
|
|
74
|
+
return arrSort;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async getSortLabelValue() {
|
|
78
|
+
const arrSort = await this.allSortLabelOptions();
|
|
79
|
+
const sortLabelwithValueList = await Promise.all(
|
|
80
|
+
arrSort.map(async (item) => ({
|
|
81
|
+
label: item,
|
|
82
|
+
value: await this.page
|
|
83
|
+
.getByRole('option', {name: item})
|
|
84
|
+
.getAttribute('data-value'),
|
|
85
|
+
}))
|
|
86
|
+
);
|
|
87
|
+
return sortLabelwithValueList;
|
|
88
|
+
}
|
|
89
|
+
}
|