@coveo/quantic 3.10.0 → 3.11.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/README.md +52 -12
- package/force-app/main/default/labels/CustomLabels.labels-meta.xml +7 -0
- package/force-app/main/default/lwc/quanticNotifications/__tests__/quanticNotifications.test.js +112 -4
- package/force-app/main/default/lwc/quanticNotifications/quanticNotifications.css +8 -0
- package/force-app/main/default/lwc/quanticNotifications/quanticNotifications.html +20 -7
- package/force-app/main/default/lwc/quanticNotifications/quanticNotifications.js +57 -18
- package/force-app/main/default/lwc/quanticUserActionsToggle/__tests__/quanticUserActionsToggle.test.js +18 -0
- package/force-app/main/default/lwc/quanticUserActionsToggle/e2e/data.ts +32 -0
- package/force-app/main/default/lwc/quanticUserActionsToggle/e2e/fixture.ts +32 -0
- package/force-app/main/default/lwc/quanticUserActionsToggle/e2e/pageObject.ts +71 -0
- package/force-app/main/default/lwc/quanticUserActionsToggle/e2e/quanticUserActionsToggle.e2e.ts +39 -0
- package/force-app/main/default/staticresources/coveoheadless/case-assist/headless.js +6 -6
- package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/build.d.ts +4 -8
- package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/common.d.ts +166 -5
- package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/core-engine.d.ts +3 -3
- package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/fetch-static-state.d.ts +4 -12
- package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/from-build-result.d.ts +1 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/hydrate-static-state.d.ts +4 -11
- package/force-app/main/default/staticresources/coveoheadless/definitions/app/ssr-engine/types/common.d.ts +6 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/context/cart/headless-cart.ssr.d.ts +2 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/context/headless-context.ssr.d.ts +2 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.d.ts +1 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/recommendations/headless-recommendations.ssr.d.ts +4 -4
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/standalone-search-box/headless-standalone-search-box.ssr.d.ts +2 -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 +11 -7
package/README.md
CHANGED
|
@@ -55,8 +55,8 @@ Or you can run the individual commands below.
|
|
|
55
55
|
- Make sure you are in the `packages/quantic` root folder.
|
|
56
56
|
- Run this command to create the default scratch org. `npm run scratch:create`.
|
|
57
57
|
- In VS Code, press Command + Shift P, enter sfdx, and select SFDX: Create a Default Scratch Org.
|
|
58
|
-
- You can also run this command from the command line. `sf org create scratch --set-default --definition-file config/
|
|
59
|
-
- `
|
|
58
|
+
- You can also run this command from the command line. `sf org create scratch --set-default --definition-file config/lws-enabled-scratch-def.json --alias Quantic__LWS_enabled`
|
|
59
|
+
- `Quantic__LWS_enabled` is an alias for the scratch org that you can use in other Salesforce CLI commands. You can create any alias that you like.
|
|
60
60
|
|
|
61
61
|
#### Deploy the Quantic code
|
|
62
62
|
|
|
@@ -64,39 +64,55 @@ Or you can run the individual commands below.
|
|
|
64
64
|
|
|
65
65
|
## Testing
|
|
66
66
|
|
|
67
|
-
### Deploy the Quantic Examples
|
|
67
|
+
### Deploy the Quantic Examples Communities
|
|
68
68
|
|
|
69
|
-
Example components are available as
|
|
69
|
+
Example components are available as Salesforce communities (Digital Experiences), allowing you to experiment with Quantic components in two separate environments: one with Lightning Web Security (LWS) enabled and one with LWS disabled.
|
|
70
70
|
|
|
71
|
-
To
|
|
71
|
+
To set up both communities in scratch orgs, run:
|
|
72
72
|
|
|
73
73
|
```bash
|
|
74
74
|
npm run setup:examples
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
This script creates, configures, and deploys everything required to have fully working examples
|
|
77
|
+
This script creates, configures, and deploys everything required to have fully working examples in **two scratch orgs**:
|
|
78
|
+
|
|
79
|
+
- An org with **LWS enabled**.
|
|
80
|
+
- An org with **LWS disabled**.
|
|
81
|
+
|
|
82
|
+
At the end of the script, the URLs for the two communities are provided, as in the following example:
|
|
78
83
|
|
|
79
84
|
```
|
|
80
|
-
...
|
|
81
85
|
|
|
82
|
-
The 'Quantic Examples' community is ready, you can access it at the following URL:
|
|
83
|
-
https://your-salesforce-scratch-org-instance.force.com/examples
|
|
86
|
+
The 'Quantic Examples' community (LWS enabled) is ready, you can access it at the following URL:
|
|
87
|
+
https://your-salesforce-lws-enabled-scratch-org-instance.force.com/examples
|
|
88
|
+
|
|
89
|
+
The 'Quantic Examples' community (LWS disabled) is ready, you can access it at the following URL:
|
|
90
|
+
https://your-salesforce-lws-disabled-scratch-org-instance.force.com/examples
|
|
84
91
|
|
|
85
92
|
To open Cypress, run:
|
|
86
93
|
npm run e2e:watch
|
|
87
94
|
```
|
|
88
95
|
|
|
89
|
-
Once the community has been deployed, you can deploy the `main` or `example` components only when needed
|
|
96
|
+
Once the community has been deployed, you can deploy the `main` or `example` components to a specific org only when needed by running the corresponding commands:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npm run deploy:main --target-org Quantic__LWS_enabled
|
|
100
|
+
npm run deploy:examples --target-org Quantic__LWS_enabled
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
You can replace Quantic\_\_LWS_enabled with your target org alias. For example:
|
|
90
104
|
|
|
91
105
|
```bash
|
|
92
|
-
npm run deploy:main
|
|
93
|
-
npm run deploy:examples
|
|
106
|
+
npm run deploy:main --target-org MyCustomOrg
|
|
107
|
+
npm run deploy:examples --target-org MyCustomOrg
|
|
94
108
|
```
|
|
95
109
|
|
|
96
110
|
### Run Cypress for Quantic Components
|
|
97
111
|
|
|
98
112
|
**Note** Before attempting to run [Cypress](https://docs.cypress.io) tests, make sure the `Quantic Examples` community is deployed as described in the previous section.
|
|
99
113
|
|
|
114
|
+
**Note** Cypress tests run on the org with **LWS enabled**, aliased as `Quantic__LWS_enabled`.
|
|
115
|
+
|
|
100
116
|
To learn how to add tests, see [adding tests](./docs/adding-tests.md).
|
|
101
117
|
|
|
102
118
|
- All the tests will need to be under folder `cypress/integration`.
|
|
@@ -119,6 +135,30 @@ To get the [detailed report](./docs/detailed-reporting.md), run:
|
|
|
119
135
|
npm run e2e:detailed
|
|
120
136
|
```
|
|
121
137
|
|
|
138
|
+
### Run Playwright for Quantic Components
|
|
139
|
+
|
|
140
|
+
**Note** Before attempting to run [Playwright](https://playwright.dev/) tests, make sure the `Quantic Examples` community is deployed as described in the previous section in both orgs, the one where LWS is enabled and the one where it is disabled.
|
|
141
|
+
|
|
142
|
+
**Note** For more information on how to add tests, please refer to our [Quantic Testing Strategy](https://github.com/coveo/ui-kit/blob/master/packages/quantic/decisions/0001-testing-strategy.md), which provides detailed guidelines on testing Quantic components.
|
|
143
|
+
|
|
144
|
+
To run Playwright tests, run:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
npm run e2e:playwright:
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
To run Playwright tests only for the scratch org where LWS is enabled, run:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
npm run e2e:playwright:lws-enabled
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
To run Playwright tests only for the scratch org where LWS is disabled, run:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npm run e2e:playwright:lws-disabled
|
|
160
|
+
```
|
|
161
|
+
|
|
122
162
|
### Run LWC unit tests for Quantic Components
|
|
123
163
|
|
|
124
164
|
To run LWC unit tests directly in your console, run:
|
|
@@ -1603,4 +1603,11 @@
|
|
|
1603
1603
|
<protected>false</protected>
|
|
1604
1604
|
<shortDescription>User actions</shortDescription>
|
|
1605
1605
|
</labels>
|
|
1606
|
+
<labels>
|
|
1607
|
+
<fullName>quantic_CloseNotification</fullName>
|
|
1608
|
+
<value>Close notification</value>
|
|
1609
|
+
<language>en_US</language>
|
|
1610
|
+
<protected>false</protected>
|
|
1611
|
+
<shortDescription>Close notification</shortDescription>
|
|
1612
|
+
</labels>
|
|
1606
1613
|
</CustomLabels>
|
package/force-app/main/default/lwc/quanticNotifications/__tests__/quanticNotifications.test.js
CHANGED
|
@@ -20,17 +20,30 @@ const exampleEngine = {
|
|
|
20
20
|
id: 'mock engine',
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
const mockSearchStatusState = {
|
|
24
|
+
hasResults: true,
|
|
25
|
+
hasError: false,
|
|
26
|
+
};
|
|
27
|
+
|
|
23
28
|
const functionsMocks = {
|
|
24
29
|
buildNotifyTrigger: jest.fn(() => ({
|
|
25
30
|
state: notificationsState,
|
|
26
31
|
subscribe: functionsMocks.subscribe,
|
|
27
32
|
})),
|
|
28
33
|
dispatchMessage: jest.fn(() => {}),
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
buildSearchStatus: jest.fn(() => ({
|
|
35
|
+
state: mockSearchStatusState,
|
|
36
|
+
subscribe: jest.fn((callback) => {
|
|
37
|
+
functionsMocks.subscribe.callback = callback;
|
|
38
|
+
return functionsMocks.unsubscribeSearchStatus;
|
|
39
|
+
}),
|
|
40
|
+
})),
|
|
41
|
+
subscribe: jest.fn((callback) => {
|
|
42
|
+
callback();
|
|
31
43
|
return functionsMocks.unsubscribe;
|
|
32
44
|
}),
|
|
33
45
|
unsubscribe: jest.fn(() => {}),
|
|
46
|
+
unsubscribeSearchStatus: jest.fn(() => {}),
|
|
34
47
|
};
|
|
35
48
|
|
|
36
49
|
// @ts-ignore
|
|
@@ -43,6 +56,7 @@ AriaLiveRegion.mockImplementation(() => {
|
|
|
43
56
|
const selectors = {
|
|
44
57
|
notifications: '[data-test="notification"]',
|
|
45
58
|
initializationError: 'c-quantic-component-error',
|
|
59
|
+
notificationCloseButton: 'lightning-button-icon',
|
|
46
60
|
};
|
|
47
61
|
|
|
48
62
|
const defaultOptions = {
|
|
@@ -68,6 +82,7 @@ function prepareHeadlessState() {
|
|
|
68
82
|
mockHeadlessLoader.getHeadlessBundle = () => {
|
|
69
83
|
return {
|
|
70
84
|
buildNotifyTrigger: functionsMocks.buildNotifyTrigger,
|
|
85
|
+
buildSearchStatus: functionsMocks.buildSearchStatus,
|
|
71
86
|
};
|
|
72
87
|
};
|
|
73
88
|
}
|
|
@@ -145,7 +160,7 @@ describe('c-quantic-notifications', () => {
|
|
|
145
160
|
});
|
|
146
161
|
|
|
147
162
|
describe('component initialization', () => {
|
|
148
|
-
it('should build the
|
|
163
|
+
it('should build the notifyTrigger and searchStatus controllers with the proper parameters', async () => {
|
|
149
164
|
createTestComponent();
|
|
150
165
|
await flushPromises();
|
|
151
166
|
|
|
@@ -153,9 +168,13 @@ describe('c-quantic-notifications', () => {
|
|
|
153
168
|
expect(functionsMocks.buildNotifyTrigger).toHaveBeenCalledWith(
|
|
154
169
|
exampleEngine
|
|
155
170
|
);
|
|
171
|
+
expect(functionsMocks.buildSearchStatus).toHaveBeenCalledTimes(1);
|
|
172
|
+
expect(functionsMocks.buildSearchStatus).toHaveBeenCalledWith(
|
|
173
|
+
exampleEngine
|
|
174
|
+
);
|
|
156
175
|
});
|
|
157
176
|
|
|
158
|
-
it('should subscribe to the headless state changes', async () => {
|
|
177
|
+
it('should subscribe to the headless notify trigger state changes', async () => {
|
|
159
178
|
createTestComponent();
|
|
160
179
|
await flushPromises();
|
|
161
180
|
|
|
@@ -223,5 +242,94 @@ describe('c-quantic-notifications', () => {
|
|
|
223
242
|
expect(notifications.length).toEqual(0);
|
|
224
243
|
});
|
|
225
244
|
});
|
|
245
|
+
|
|
246
|
+
describe('when there is an error following a search', () => {
|
|
247
|
+
it('should not render the notifications component', async () => {
|
|
248
|
+
const element = createTestComponent();
|
|
249
|
+
mockSearchStatusState.hasError = true;
|
|
250
|
+
functionsMocks.subscribe.callback();
|
|
251
|
+
await flushPromises();
|
|
252
|
+
|
|
253
|
+
const notifications = element.shadowRoot.querySelectorAll(
|
|
254
|
+
selectors.notifications
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
expect(notifications.length).toEqual(0);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
describe('when clicking on a notification close button', () => {
|
|
263
|
+
it('should properly dismiss that notification', async () => {
|
|
264
|
+
const element = createTestComponent();
|
|
265
|
+
await flushPromises();
|
|
266
|
+
|
|
267
|
+
const notificationsBeforeClose = element.shadowRoot.querySelectorAll(
|
|
268
|
+
selectors.notifications
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
const firstNotificationCloseButton =
|
|
272
|
+
notificationsBeforeClose[0].querySelector(
|
|
273
|
+
selectors.notificationCloseButton
|
|
274
|
+
);
|
|
275
|
+
firstNotificationCloseButton.click();
|
|
276
|
+
await flushPromises();
|
|
277
|
+
|
|
278
|
+
const notificationsAfterClose = element.shadowRoot.querySelectorAll(
|
|
279
|
+
selectors.notifications
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
expect(notificationsAfterClose.length).toEqual(
|
|
283
|
+
exampleNotifications.length - 1
|
|
284
|
+
);
|
|
285
|
+
expect(notificationsAfterClose[0].textContent).toEqual(
|
|
286
|
+
exampleNotifications[1]
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
describe('when triggering another search with the same query', () => {
|
|
291
|
+
it('should reset the visibility of the notifications', async () => {
|
|
292
|
+
const element = createTestComponent();
|
|
293
|
+
await flushPromises();
|
|
294
|
+
|
|
295
|
+
const notificationsBeforeClose = element.shadowRoot.querySelectorAll(
|
|
296
|
+
selectors.notifications
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
expect(notificationsBeforeClose.length).toEqual(
|
|
300
|
+
exampleNotifications.length
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
const firstNotificationCloseButton =
|
|
304
|
+
notificationsBeforeClose[0].querySelector(
|
|
305
|
+
selectors.notificationCloseButton
|
|
306
|
+
);
|
|
307
|
+
firstNotificationCloseButton.click();
|
|
308
|
+
await flushPromises();
|
|
309
|
+
|
|
310
|
+
const notificationsAfterClose = element.shadowRoot.querySelectorAll(
|
|
311
|
+
selectors.notifications
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
expect(notificationsAfterClose.length).toEqual(
|
|
315
|
+
exampleNotifications.length - 1
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
mockSearchStatusState.hasError = false;
|
|
319
|
+
functionsMocks.subscribe.callback();
|
|
320
|
+
await flushPromises();
|
|
321
|
+
|
|
322
|
+
const notificationsAfterSearch = element.shadowRoot.querySelectorAll(
|
|
323
|
+
selectors.notifications
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
expect(notificationsAfterSearch.length).toEqual(
|
|
327
|
+
exampleNotifications.length
|
|
328
|
+
);
|
|
329
|
+
notificationsAfterSearch.forEach((notification, index) => {
|
|
330
|
+
expect(notification.textContent).toEqual(exampleNotifications[index]);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
});
|
|
226
334
|
});
|
|
227
335
|
});
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
--slds-c-toast-sizing-min-width: 100%;
|
|
3
|
+
}
|
|
4
|
+
|
|
1
5
|
.notification {
|
|
2
6
|
background-color: var(---lwc-colorBackgroundAlt, white);
|
|
3
7
|
border: 1px solid var(--lwc-colorBackgroundToast, rgb(116, 116, 116));
|
|
4
8
|
margin-right: 0;
|
|
5
9
|
margin-left: 0;
|
|
6
10
|
width: 100%;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.notification-container {
|
|
14
|
+
z-index: 2;
|
|
7
15
|
}
|
|
@@ -5,15 +5,28 @@
|
|
|
5
5
|
</template>
|
|
6
6
|
<template lwc:else>
|
|
7
7
|
<template for:each={notifications} for:item="notification">
|
|
8
|
-
<
|
|
9
|
-
<div data-
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
<template lwc:if={notification.visible}>
|
|
9
|
+
<div key={notification.id} data-test="notification" class="slds-notify_container slds-is-relative notification-container">
|
|
10
|
+
<div data-cy="notification" class="slds-notify slds-notify_toast notification" role="status">
|
|
11
|
+
<lightning-icon icon-name="utility:info_alt" alternative-text="info" title="info"
|
|
12
|
+
size="small"></lightning-icon>
|
|
13
|
+
<div class="slds-notify__content slds-var-m-left_small">
|
|
14
|
+
{notification.value}
|
|
15
|
+
</div>
|
|
16
|
+
<div class="slds-notify__close">
|
|
17
|
+
<lightning-button-icon
|
|
18
|
+
icon-name="utility:close"
|
|
19
|
+
alternative-text={labels.closeNotification}
|
|
20
|
+
size="small"
|
|
21
|
+
variant="bare"
|
|
22
|
+
title={labels.closeNotification}
|
|
23
|
+
onclick={handleNotificationClose}
|
|
24
|
+
data-id={notification.id}
|
|
25
|
+
></lightning-button-icon>
|
|
26
|
+
</div>
|
|
14
27
|
</div>
|
|
15
28
|
</div>
|
|
16
|
-
</
|
|
29
|
+
</template>
|
|
17
30
|
</template>
|
|
18
31
|
</template>
|
|
19
32
|
</template>
|
|
@@ -3,12 +3,13 @@ import {
|
|
|
3
3
|
initializeWithHeadless,
|
|
4
4
|
getHeadlessBundle,
|
|
5
5
|
} from 'c/quanticHeadlessLoader';
|
|
6
|
+
import closeNotification from '@salesforce/label/c.quantic_CloseNotification';
|
|
6
7
|
import {AriaLiveRegion} from 'c/quanticUtils';
|
|
7
8
|
import {LightningElement, api} from 'lwc';
|
|
8
9
|
|
|
9
10
|
/** @typedef {import("coveo").SearchEngine} SearchEngine */
|
|
11
|
+
/** @typedef {import("coveo").SearchStatus} SearchStatus */
|
|
10
12
|
/** @typedef {import("coveo").NotifyTrigger} NotifyTrigger */
|
|
11
|
-
/** @typedef {import("coveo").NotifyTriggerState} NotifyTriggerState */
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* The `QuanticNotifications` component is responsible for displaying notifications generated by the Coveo Search API (see [Trigger](https://docs.coveo.com/en/1458)).
|
|
@@ -27,12 +28,22 @@ export default class QuanticNotifications extends LightningElement {
|
|
|
27
28
|
|
|
28
29
|
/** @type {NotifyTrigger} */
|
|
29
30
|
notifyTrigger;
|
|
30
|
-
/** @type {NotifyTriggerState} */
|
|
31
|
-
notifyTriggerState;
|
|
32
31
|
/** @type {boolean} */
|
|
33
32
|
hasInitializationError = false;
|
|
34
33
|
/** @type {import('c/quanticUtils').AriaLiveUtils} */
|
|
35
34
|
ariaLiveNotificationsRegion;
|
|
35
|
+
/** @type {Array} */
|
|
36
|
+
notifications = [];
|
|
37
|
+
/** @type {Function} */
|
|
38
|
+
unsubscribe;
|
|
39
|
+
/** @type {Function} */
|
|
40
|
+
unsubscribeSearchStatus;
|
|
41
|
+
/** @type {SearchStatus} */
|
|
42
|
+
searchStatus;
|
|
43
|
+
|
|
44
|
+
labels = {
|
|
45
|
+
closeNotification,
|
|
46
|
+
};
|
|
36
47
|
|
|
37
48
|
connectedCallback() {
|
|
38
49
|
registerComponentForInit(this, this.engineId);
|
|
@@ -48,32 +59,60 @@ export default class QuanticNotifications extends LightningElement {
|
|
|
48
59
|
initialize = (engine) => {
|
|
49
60
|
this.headless = getHeadlessBundle(this.engineId);
|
|
50
61
|
this.notifyTrigger = this.headless.buildNotifyTrigger(engine);
|
|
62
|
+
this.searchStatus = this.headless.buildSearchStatus(engine);
|
|
51
63
|
this.ariaLiveNotificationsRegion = AriaLiveRegion('notifications', this);
|
|
52
64
|
this.unsubscribe = this.notifyTrigger.subscribe(() => this.updateState());
|
|
65
|
+
this.unsubscribeSearchStatus = this.searchStatus.subscribe(() =>
|
|
66
|
+
this.handleSearchStatusChange()
|
|
67
|
+
);
|
|
53
68
|
};
|
|
54
69
|
|
|
55
70
|
disconnectedCallback() {
|
|
56
71
|
this.unsubscribe?.();
|
|
72
|
+
this.unsubscribeSearchStatus?.();
|
|
57
73
|
}
|
|
58
74
|
|
|
59
75
|
updateState() {
|
|
60
|
-
this.
|
|
61
|
-
|
|
62
|
-
this.notifyTriggerState?.notifications.reduce(
|
|
63
|
-
(value, notification, index) => {
|
|
64
|
-
return `${value} Notification ${index + 1}: ${notification}`;
|
|
65
|
-
},
|
|
66
|
-
''
|
|
67
|
-
)
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
get notifications() {
|
|
72
|
-
return (
|
|
73
|
-
this.notifyTriggerState?.notifications.map((notification, index) => ({
|
|
76
|
+
this.notifications =
|
|
77
|
+
this.notifyTrigger?.state?.notifications.map((notification, index) => ({
|
|
74
78
|
value: notification,
|
|
75
79
|
id: index,
|
|
76
|
-
|
|
80
|
+
visible: true,
|
|
81
|
+
})) ?? [];
|
|
82
|
+
|
|
83
|
+
this.dispatchAriaLiveNotificationsRegionMessage();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
handleSearchStatusChange() {
|
|
87
|
+
if (!this.searchStatus?.state.isLoading) {
|
|
88
|
+
if (this.searchStatus?.state?.hasError) {
|
|
89
|
+
this.notifications = [];
|
|
90
|
+
} else {
|
|
91
|
+
this.notifications =
|
|
92
|
+
this.notifications?.map((notification) => ({
|
|
93
|
+
...notification,
|
|
94
|
+
visible: true,
|
|
95
|
+
})) ?? [];
|
|
96
|
+
this.dispatchAriaLiveNotificationsRegionMessage();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
handleNotificationClose(event) {
|
|
102
|
+
const currentNotificationId = event.currentTarget.dataset.id;
|
|
103
|
+
this.notifications = this.notifications.map((notification) => {
|
|
104
|
+
if (notification.id.toString() === currentNotificationId) {
|
|
105
|
+
return {...notification, visible: false};
|
|
106
|
+
}
|
|
107
|
+
return notification;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
dispatchAriaLiveNotificationsRegionMessage() {
|
|
112
|
+
this.ariaLiveNotificationsRegion.dispatchMessage(
|
|
113
|
+
this.notifications.reduce((value, notification) => {
|
|
114
|
+
return `${value} Notification ${notification.id + 1}: ${notification.value}`;
|
|
115
|
+
}, '')
|
|
77
116
|
);
|
|
78
117
|
}
|
|
79
118
|
|
|
@@ -119,6 +119,24 @@ describe('c-quantic-user-actions-toggle', () => {
|
|
|
119
119
|
cleanup();
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
+
describe('controller initialization', () => {
|
|
123
|
+
it('should build the controller with the right parameters', async () => {
|
|
124
|
+
createTestComponent();
|
|
125
|
+
await flushPromises();
|
|
126
|
+
|
|
127
|
+
expect(functionsMocks.buildUserActions).toHaveBeenCalledTimes(1);
|
|
128
|
+
expect(functionsMocks.buildUserActions).toHaveBeenCalledWith(
|
|
129
|
+
exampleEngine,
|
|
130
|
+
{
|
|
131
|
+
options: {
|
|
132
|
+
ticketCreationDate: exampleTicketCreationDateTime,
|
|
133
|
+
excludedCustomActions: exampleExcludedCustomActions,
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
122
140
|
it('should display the user actions toggle button', async () => {
|
|
123
141
|
const element = createTestComponent();
|
|
124
142
|
await flushPromises();
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const followingSessionsActions = [
|
|
2
|
+
{
|
|
3
|
+
name: 'CUSTOM',
|
|
4
|
+
value:
|
|
5
|
+
'{"event_type":"example","event_value":"exampleCustomAction","origin_level_1":"default"}',
|
|
6
|
+
time: new Date('2024-09-01T15:30:00Z').valueOf(),
|
|
7
|
+
},
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
const ticketCreationSessionActions = [
|
|
11
|
+
{
|
|
12
|
+
name: 'CUSTOM',
|
|
13
|
+
value:
|
|
14
|
+
'{"event_type":"errors","event_value":"One","origin_level_1":"default","origin_level_2":"default"}',
|
|
15
|
+
time: new Date('2024-08-30T00:10:00Z').valueOf(),
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const precedingSessionsActions = [
|
|
20
|
+
{
|
|
21
|
+
name: 'CUSTOM',
|
|
22
|
+
value:
|
|
23
|
+
'{"event_type":"example","event_value":"exampleCustomAction","origin_level_1":"default"}',
|
|
24
|
+
time: new Date('2024-08-29T15:40:00Z').valueOf(),
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export const exampleUserActionsData = [
|
|
29
|
+
...followingSessionsActions,
|
|
30
|
+
...ticketCreationSessionActions,
|
|
31
|
+
...precedingSessionsActions,
|
|
32
|
+
];
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {quanticBase} from '../../../../../../playwright/fixtures/baseFixture';
|
|
2
|
+
import {UserActionsToggleObject} from './pageObject';
|
|
3
|
+
import {exampleUserActionsData} from './data';
|
|
4
|
+
|
|
5
|
+
const pageUrl = 's/quantic-user-actions-toggle';
|
|
6
|
+
|
|
7
|
+
interface UserActionsToggleOptions {
|
|
8
|
+
userId: string;
|
|
9
|
+
ticketCreationDateTime: string;
|
|
10
|
+
}
|
|
11
|
+
type QuanticUserActionsToggleE2EFixtures = {
|
|
12
|
+
userActionsToggle: UserActionsToggleObject;
|
|
13
|
+
options: UserActionsToggleOptions;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const testInsight =
|
|
17
|
+
quanticBase.extend<QuanticUserActionsToggleE2EFixtures>({
|
|
18
|
+
options: {
|
|
19
|
+
userId: 'someone@company.com',
|
|
20
|
+
ticketCreationDateTime: '2024-01-01T00:00:00Z',
|
|
21
|
+
},
|
|
22
|
+
userActionsToggle: async ({page, options, configuration}, use) => {
|
|
23
|
+
await page.goto(pageUrl);
|
|
24
|
+
configuration.configure({...options});
|
|
25
|
+
const userActionsToggleObject = new UserActionsToggleObject(page);
|
|
26
|
+
userActionsToggleObject.mockUserActions(exampleUserActionsData);
|
|
27
|
+
await userActionsToggleObject.waitForUserActionsResponse(options.userId);
|
|
28
|
+
await use(userActionsToggleObject);
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export {expect} from '@playwright/test';
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import {Locator, Page, Request, Response} from '@playwright/test';
|
|
2
|
+
import {isUaCustomEvent} from '../../../../../../playwright/utils/requests';
|
|
3
|
+
|
|
4
|
+
const userActionsRequestRegex =
|
|
5
|
+
/\/rest\/organizations\/.*\/machinelearning\/user\/actions$/;
|
|
6
|
+
|
|
7
|
+
export class UserActionsToggleObject {
|
|
8
|
+
constructor(public page: Page) {
|
|
9
|
+
this.page = page;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get userActionsToggleButton(): Locator {
|
|
13
|
+
return this.page.getByRole('button', {name: /User actions/i});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get userActionsModal(): Locator {
|
|
17
|
+
return this.page.locator(
|
|
18
|
+
'c-quantic-user-actions-toggle > c-quantic-modal > div'
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get userActionsModalCloseButton(): Locator {
|
|
23
|
+
return this.page
|
|
24
|
+
.locator('c-quantic-user-actions-toggle > c-quantic-modal')
|
|
25
|
+
.getByRole('button', {name: 'Close'});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async mockUserActions(
|
|
29
|
+
userActions: Array<{name: string; value: string; time: number}>
|
|
30
|
+
) {
|
|
31
|
+
await this.page.route(userActionsRequestRegex, async (route) => {
|
|
32
|
+
const body = {value: userActions};
|
|
33
|
+
|
|
34
|
+
await route.fulfill({
|
|
35
|
+
body: JSON.stringify(body),
|
|
36
|
+
status: 200,
|
|
37
|
+
headers: {
|
|
38
|
+
'content-type': 'text/html',
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async waitForUserActionsResponse(userId: string): Promise<Response> {
|
|
45
|
+
return this.page.waitForResponse((response) => {
|
|
46
|
+
const request = response.request();
|
|
47
|
+
if (request.url().match(userActionsRequestRegex)) {
|
|
48
|
+
const requestBody = request.postDataJSON?.();
|
|
49
|
+
return requestBody?.objectId === userId;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async waitForOpenUserActionsUaAnalytics(): Promise<Request> {
|
|
56
|
+
const uaRequest = this.page.waitForRequest((request) => {
|
|
57
|
+
if (isUaCustomEvent(request)) {
|
|
58
|
+
const requestBody = request.postDataJSON?.();
|
|
59
|
+
const expectedFields = {
|
|
60
|
+
eventType: 'User Actions',
|
|
61
|
+
eventValue: 'openUserActions',
|
|
62
|
+
};
|
|
63
|
+
return Object.keys(expectedFields).every(
|
|
64
|
+
(key) => requestBody?.[key] === expectedFields[key]
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
});
|
|
69
|
+
return uaRequest;
|
|
70
|
+
}
|
|
71
|
+
}
|
package/force-app/main/default/lwc/quanticUserActionsToggle/e2e/quanticUserActionsToggle.e2e.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {testInsight, expect} from './fixture';
|
|
2
|
+
|
|
3
|
+
const exampleUserId = 'exampleUserId';
|
|
4
|
+
const exampleTicketCreateDateTime = encodeURIComponent('2024-08-30');
|
|
5
|
+
|
|
6
|
+
testInsight.use({
|
|
7
|
+
options: {
|
|
8
|
+
userId: exampleUserId,
|
|
9
|
+
ticketCreationDateTime: exampleTicketCreateDateTime,
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
testInsight.describe('quantic user actions toggle', () => {
|
|
14
|
+
testInsight.describe('when opening the user action modal', () => {
|
|
15
|
+
testInsight(
|
|
16
|
+
'should open the user actions timeline and log analytics',
|
|
17
|
+
async ({userActionsToggle}) => {
|
|
18
|
+
await expect(userActionsToggle.userActionsModal).not.toBeVisible();
|
|
19
|
+
const uaPromise = userActionsToggle.waitForOpenUserActionsUaAnalytics();
|
|
20
|
+
await userActionsToggle.userActionsToggleButton.click();
|
|
21
|
+
await expect(userActionsToggle.userActionsModal).toBeVisible();
|
|
22
|
+
await uaPromise;
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
testInsight.describe('when closing the user action modal', () => {
|
|
28
|
+
testInsight(
|
|
29
|
+
'should close the user actions timeline',
|
|
30
|
+
async ({userActionsToggle}) => {
|
|
31
|
+
await expect(userActionsToggle.userActionsModal).not.toBeVisible();
|
|
32
|
+
await userActionsToggle.userActionsToggleButton.click();
|
|
33
|
+
await expect(userActionsToggle.userActionsModal).toBeVisible();
|
|
34
|
+
await userActionsToggle.userActionsModalCloseButton.click();
|
|
35
|
+
await expect(userActionsToggle.userActionsModal).not.toBeVisible();
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
});
|