@coveo/quantic 3.13.1 → 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 +20 -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/quanticFeedbackModal/success.html +1 -1
- package/force-app/main/default/lwc/quanticFeedbackModalQna/templates/success.html +1 -1
- package/force-app/main/default/lwc/quanticGeneratedAnswer/__tests__/quanticGeneratedAnswer.test.js +113 -6
- package/force-app/main/default/lwc/quanticGeneratedAnswer/quanticGeneratedAnswer.js +33 -6
- 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/__tests__/quanticSmartSnippet.test.js +0 -16
- 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 +9 -1
- package/force-app/main/default/lwc/quanticSmartSnippet/e2e/quanticSmartSnippet.e2e.ts +51 -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/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 +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 +2 -4
- 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 -4
- 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/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 +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/force-app/main/default/staticresources/dompurify/purify.min.js +2 -2
- package/package.json +13 -13
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {testSearch, testInsight, expect} from './fixture';
|
|
2
|
+
import {
|
|
3
|
+
useCaseEnum,
|
|
4
|
+
useCaseTestCases,
|
|
5
|
+
} from '../../../../../../playwright/utils/useCase';
|
|
6
|
+
|
|
7
|
+
const fixtures = {
|
|
8
|
+
search: testSearch as typeof testSearch,
|
|
9
|
+
insight: testInsight as typeof testInsight,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
useCaseTestCases.forEach((useCase) => {
|
|
13
|
+
let test;
|
|
14
|
+
let sortArrOptions;
|
|
15
|
+
let sortLabelWithValue;
|
|
16
|
+
if (useCase.value === useCaseEnum.search) {
|
|
17
|
+
test = fixtures[useCase.value] as typeof testSearch;
|
|
18
|
+
} else {
|
|
19
|
+
test = fixtures[useCase.value] as typeof testInsight;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test.describe(`quantic sort ${useCase.label}`, () => {
|
|
23
|
+
test.beforeEach(async ({sort}) => {
|
|
24
|
+
await sort.clickSortDropDown();
|
|
25
|
+
sortArrOptions = await sort.allSortLabelOptions();
|
|
26
|
+
sortLabelWithValue = await sort.getSortLabelValue();
|
|
27
|
+
await sort.clickSortDropDown();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test.describe(`when changing sort option to 2nd position`, () => {
|
|
31
|
+
test('should trigger a new search and log analytics', async ({
|
|
32
|
+
sort,
|
|
33
|
+
search,
|
|
34
|
+
}) => {
|
|
35
|
+
const {label: expectedSortName, value: expectedSortValue} =
|
|
36
|
+
sortLabelWithValue[1]; // Assuming 0 is the first sort option, 1 should be the 2nd one after that.
|
|
37
|
+
|
|
38
|
+
const searchResponsePromise = search.waitForSearchResponse();
|
|
39
|
+
await sort.clickSortDropDown();
|
|
40
|
+
await sort.clickSortButton(expectedSortName);
|
|
41
|
+
|
|
42
|
+
const searchResponse = await searchResponsePromise;
|
|
43
|
+
const {sortCriteria} = searchResponse.request().postDataJSON();
|
|
44
|
+
expect(sortCriteria).toBe(expectedSortValue);
|
|
45
|
+
await sort.waitForSortUaAnalytics(expectedSortValue);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test.describe('when testing accessibility', () => {
|
|
50
|
+
test('should be accessible to keyboard', async ({sort, page}) => {
|
|
51
|
+
let selectedSortLabel = await sort.sortDropDown.textContent();
|
|
52
|
+
expect(selectedSortLabel).toEqual(sortArrOptions[0]);
|
|
53
|
+
|
|
54
|
+
// Selecting the next sort using Enter to open dropdown, the ArrowDown, then ENTER key
|
|
55
|
+
await sort.focusSortDropDown();
|
|
56
|
+
await sort.openSortDropdownUsingKeyboardEnter();
|
|
57
|
+
await page.keyboard.press('ArrowDown');
|
|
58
|
+
await page.keyboard.press('Enter');
|
|
59
|
+
selectedSortLabel = await sort.sortDropDown.textContent();
|
|
60
|
+
expect(selectedSortLabel).toEqual(sortArrOptions[1]);
|
|
61
|
+
|
|
62
|
+
// Selecting the next sort using Space to open dropdown, the ArrowDown, then ENTER key
|
|
63
|
+
await sort.focusSortDropDown();
|
|
64
|
+
await sort.openSortDropdownUsingKeyboardEnter(false);
|
|
65
|
+
await page.keyboard.press('ArrowDown');
|
|
66
|
+
await page.keyboard.press('Enter');
|
|
67
|
+
selectedSortLabel = await sort.sortDropDown.textContent();
|
|
68
|
+
expect(selectedSortLabel).toEqual(sortArrOptions[2]);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
if (useCase.value === 'search') {
|
|
73
|
+
test.describe('when loading options from the url', () => {
|
|
74
|
+
test('should reflect the options of url in the component', async ({
|
|
75
|
+
sort,
|
|
76
|
+
page,
|
|
77
|
+
}) => {
|
|
78
|
+
const {label: expectedSortName, value: expectedSortValue} =
|
|
79
|
+
sortLabelWithValue[2]; // Assuming 0 is the first sort option
|
|
80
|
+
|
|
81
|
+
const currentUrl = await page.url();
|
|
82
|
+
const urlHash = `#sortCriteria=${encodeURI(expectedSortValue)}`;
|
|
83
|
+
|
|
84
|
+
await page.goto(`${currentUrl}/${urlHash}`);
|
|
85
|
+
await page.getByRole('button', {name: 'Try it now'}).click();
|
|
86
|
+
await expect(sort.sortDropDown).toContainText(expectedSortName);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -167,11 +167,12 @@ export default class QuanticSort extends LightningElement {
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
/**
|
|
170
|
-
* @param {CustomEvent<{value: string}>}
|
|
170
|
+
* @param {CustomEvent<{value: string}>} event
|
|
171
171
|
*/
|
|
172
|
-
handleChange(
|
|
172
|
+
handleChange(event) {
|
|
173
173
|
this.sort.sortBy(
|
|
174
|
-
this.options.find((option) => option.value ===
|
|
174
|
+
this.options.find((option) => option.value === event.detail.value)
|
|
175
|
+
.criterion
|
|
175
176
|
);
|
|
176
177
|
}
|
|
177
178
|
|
|
@@ -280,8 +281,10 @@ export default class QuanticSort extends LightningElement {
|
|
|
280
281
|
* @returns {SortOption[]} The specified custom sort options.
|
|
281
282
|
*/
|
|
282
283
|
get customSortOptions() {
|
|
284
|
+
/** @type {HTMLSlotElement} */
|
|
285
|
+
const slot = this.template.querySelector('slot[name="sortOption"]');
|
|
283
286
|
/** @type {SortOption[]} */
|
|
284
|
-
return Array.from(
|
|
287
|
+
return Array.from(slot?.assignedNodes()).map(
|
|
285
288
|
// @ts-ignore
|
|
286
289
|
({label, value, criterion}) => ({label, value, criterion})
|
|
287
290
|
);
|