@coveo/quantic 3.10.0-pre.faaf12d7a7 → 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.
Files changed (80) hide show
  1. package/README.md +52 -12
  2. package/force-app/main/default/labels/CustomLabels.labels-meta.xml +7 -0
  3. package/force-app/main/default/lwc/quanticCaseAssistInterface/quanticCaseAssistInterface.js +8 -0
  4. package/force-app/main/default/lwc/quanticCategoryFacet/__tests__/quanticCategoryFacet.test.js +108 -1
  5. package/force-app/main/default/lwc/quanticCategoryFacet/quanticCategoryFacet.js +22 -0
  6. package/force-app/main/default/lwc/quanticDateFacet/__tests__/quanticDateFacet.test.js +107 -1
  7. package/force-app/main/default/lwc/quanticDateFacet/quanticDateFacet.js +22 -0
  8. package/force-app/main/default/lwc/quanticFacet/__tests__/quanticFacet.test.js +106 -1
  9. package/force-app/main/default/lwc/quanticFacet/quanticFacet.js +21 -1
  10. package/force-app/main/default/lwc/quanticFeedbackModalQna/__tests__/quanticFeedbackModalQna.test.js +1 -1
  11. package/force-app/main/default/lwc/quanticFeedbackModalQna/quanticFeedbackModalQna.js +1 -1
  12. package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/generatedAnswer.css +0 -1
  13. package/force-app/main/default/lwc/quanticGeneratedAnswerContent/templates/generatedMarkdownContent.css +1 -0
  14. package/force-app/main/default/lwc/quanticInsightInterface/quanticInsightInterface.js +8 -0
  15. package/force-app/main/default/lwc/quanticNotifications/__tests__/quanticNotifications.test.js +112 -4
  16. package/force-app/main/default/lwc/quanticNotifications/quanticNotifications.css +8 -0
  17. package/force-app/main/default/lwc/quanticNotifications/quanticNotifications.html +20 -7
  18. package/force-app/main/default/lwc/quanticNotifications/quanticNotifications.js +57 -18
  19. package/force-app/main/default/lwc/quanticNumericFacet/__tests__/quanticNumericFacet.test.js +107 -1
  20. package/force-app/main/default/lwc/quanticNumericFacet/quanticNumericFacet.js +22 -0
  21. package/force-app/main/default/lwc/quanticRecommendationInterface/quanticRecommendationInterface.js +8 -0
  22. package/force-app/main/default/lwc/quanticSearchInterface/quanticSearchInterface.js +8 -0
  23. package/force-app/main/default/lwc/quanticTimeframeFacet/__tests__/quanticTimeframeFacet.test.js +107 -1
  24. package/force-app/main/default/lwc/quanticTimeframeFacet/quanticTimeframeFacet.js +22 -0
  25. package/force-app/main/default/lwc/quanticUserActionsToggle/__tests__/quanticUserActionsToggle.test.js +18 -0
  26. package/force-app/main/default/lwc/quanticUserActionsToggle/e2e/data.ts +32 -0
  27. package/force-app/main/default/lwc/quanticUserActionsToggle/e2e/fixture.ts +32 -0
  28. package/force-app/main/default/lwc/quanticUserActionsToggle/e2e/pageObject.ts +71 -0
  29. package/force-app/main/default/lwc/quanticUserActionsToggle/e2e/quanticUserActionsToggle.e2e.ts +39 -0
  30. package/force-app/main/default/staticresources/coveobueno/browser/bueno.js +3 -18
  31. package/force-app/main/default/staticresources/coveoheadless/case-assist/headless.js +9 -6
  32. package/force-app/main/default/staticresources/coveoheadless/definitions/api/commerce/common/request.d.ts +1 -1
  33. package/force-app/main/default/staticresources/coveoheadless/definitions/api/commerce/recommendations/recommendations-request.d.ts +1 -1
  34. package/force-app/main/default/staticresources/coveoheadless/definitions/api/commerce/search/query-suggest/query-suggest-request.d.ts +1 -1
  35. package/force-app/main/default/staticresources/coveoheadless/definitions/api/knowledge/stream-answer-api.d.ts +1 -1
  36. package/force-app/main/default/staticresources/coveoheadless/definitions/api/service/case-assist/get-case-classifications/get-case-classifications-request.d.ts +1 -1
  37. package/force-app/main/default/staticresources/coveoheadless/definitions/api/service/case-assist/get-document-suggestions/get-document-suggestions-request.d.ts +1 -1
  38. package/force-app/main/default/staticresources/coveoheadless/definitions/api/service/insight/get-interface/get-interface-config-request.d.ts +1 -1
  39. package/force-app/main/default/staticresources/coveoheadless/definitions/api/service/insight/query/query-request.d.ts +2 -2
  40. package/force-app/main/default/staticresources/coveoheadless/definitions/api/service/insight/user-actions/user-actions-request.d.ts +1 -1
  41. package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-engine/commerce-engine.ssr.d.ts +4 -13
  42. package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/common.d.ts +8 -3
  43. package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/factories/build-factory.d.ts +19 -0
  44. package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/factories/hydrated-state-factory.d.ts +4 -0
  45. package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/factories/recommendation-hydrated-state-factory.d.ts +3 -0
  46. package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/factories/recommendation-static-state-factory.d.ts +3 -0
  47. package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/factories/static-state-factory.d.ts +4 -0
  48. package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/build.d.ts +13 -0
  49. package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/common.d.ts +180 -5
  50. package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/core-engine.d.ts +29 -10
  51. package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/fetch-static-state.d.ts +15 -0
  52. package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/from-build-result.d.ts +20 -0
  53. package/force-app/main/default/staticresources/coveoheadless/definitions/app/commerce-ssr-engine/types/hydrate-static-state.d.ts +18 -0
  54. package/force-app/main/default/staticresources/coveoheadless/definitions/app/logger.d.ts +1 -1
  55. package/force-app/main/default/staticresources/coveoheadless/definitions/app/navigatorContextProvider.d.ts +17 -0
  56. package/force-app/main/default/staticresources/coveoheadless/definitions/app/ssr-engine/types/common.d.ts +6 -0
  57. package/force-app/main/default/staticresources/coveoheadless/definitions/app/ssr-engine/types/from-build-result.d.ts +1 -0
  58. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/parameter-manager/headless-core-parameter-manager.d.ts +1 -1
  59. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/field-suggestions/headless-field-suggestions-generator.ssr.d.ts +2 -2
  60. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/instant-products/headless-instant-products.ssr.d.ts +3 -3
  61. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/product-view/headless-product-view.ssr.d.ts +2 -2
  62. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/recent-queries-list/headless-recent-queries-list.ssr.d.ts +2 -2
  63. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/recommendations/headless-recommendations.ssr.d.ts +10 -4
  64. package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/standalone-search-box/headless-standalone-search-box.ssr.d.ts +2 -2
  65. package/force-app/main/default/staticresources/coveoheadless/definitions/features/analytics/search-action-cause.d.ts +0 -82
  66. package/force-app/main/default/staticresources/coveoheadless/definitions/features/facets/facet-set/facet-set-analytics-actions.d.ts +0 -1
  67. package/force-app/main/default/staticresources/coveoheadless/definitions/features/history/history-analytics-actions.d.ts +0 -3
  68. package/force-app/main/default/staticresources/coveoheadless/definitions/features/search/legacy/search-request.d.ts +1 -1
  69. package/force-app/main/default/staticresources/coveoheadless/definitions/features/search/search-actions.d.ts +2 -2
  70. package/force-app/main/default/staticresources/coveoheadless/definitions/features/search/search-request.d.ts +1 -1
  71. package/force-app/main/default/staticresources/coveoheadless/definitions/features/static-filter-set/static-filter-set-actions.d.ts +0 -4
  72. package/force-app/main/default/staticresources/coveoheadless/definitions/ssr-commerce.index.d.ts +4 -4
  73. package/force-app/main/default/staticresources/coveoheadless/definitions/test/mock-engine-v2.d.ts +1 -1
  74. package/force-app/main/default/staticresources/coveoheadless/definitions/utils/facet-utils.d.ts +1 -1
  75. package/force-app/main/default/staticresources/coveoheadless/definitions/utils/utils.d.ts +4 -0
  76. package/force-app/main/default/staticresources/coveoheadless/headless.js +11 -8
  77. package/force-app/main/default/staticresources/coveoheadless/insight/headless.js +10 -7
  78. package/force-app/main/default/staticresources/coveoheadless/recommendation/headless.js +9 -6
  79. package/force-app/main/default/staticresources/dompurify/purify.min.js +1339 -2
  80. package/package.json +23 -19
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/project-scratch-def.json --alias "LWC"`
59
- - `LWC` is an alias for the scratch org that you can use in other Salesforce CLI commands. You can create any alias that you like.
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 Community
67
+ ### Deploy the Quantic Examples Communities
68
68
 
69
- Example components are available as a Salesforce community (Digital Experience) allowing you to experiment with Quantic components.
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 setup the community in the `LWC` scratch org, run:
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. The community URL is provided at the end of the script output, as in the following example:
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. To do so, run:
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>
@@ -64,6 +64,14 @@ export default class QuanticCaseAssistInterface extends LightningElement {
64
64
  ...(document.referrer && {
65
65
  originLevel3: document.referrer.substring(0, 256),
66
66
  }),
67
+ analyticsClientMiddleware: (_event, payload) => {
68
+ if (!payload.customData) {
69
+ payload.customData = {};
70
+ }
71
+ payload.customData.coveoQuanticVersion =
72
+ window.coveoQuanticVersion;
73
+ return payload;
74
+ },
67
75
  },
68
76
  ...rest,
69
77
  },
@@ -15,16 +15,21 @@ jest.mock('c/quanticUtils', () => ({
15
15
  CATEGORYFACETS: 'categoryFacets',
16
16
  },
17
17
  },
18
+ I18nUtils: {
19
+ format: jest.fn(),
20
+ },
18
21
  }));
19
22
  jest.mock('c/quanticHeadlessLoader');
20
23
 
21
24
  const selectors = {
22
25
  facetContent: '[data-test="facet-content"]',
26
+ componentError: 'c-quantic-component-error',
23
27
  };
24
28
 
25
29
  const exampleFacetId = 'example facet id';
30
+ const exampleField = 'exampleField';
26
31
  const defaultOptions = {
27
- field: 'example field',
32
+ field: exampleField,
28
33
  };
29
34
  const categoryFacetControllerMock = {
30
35
  subscribe: jest.fn((callback) => callback()),
@@ -33,6 +38,8 @@ const categoryFacetControllerMock = {
33
38
  values: [],
34
39
  },
35
40
  };
41
+ const parentFacetIdError = `The ${exampleField} c-quantic-category-facet requires dependsOn.parentFacetId to be a valid string.`;
42
+ const expectedValueError = `The ${exampleField} c-quantic-category-facet requires dependsOn.expectedValue to be a valid string.`;
36
43
 
37
44
  function createTestComponent(options = defaultOptions) {
38
45
  prepareHeadlessState();
@@ -82,6 +89,21 @@ const exampleEngine = {
82
89
  };
83
90
  let isInitialized = false;
84
91
 
92
+ function mockBueno() {
93
+ // @ts-ignore
94
+ mockHeadlessLoader.getBueno = () => {
95
+ // @ts-ignore
96
+ global.Bueno = {
97
+ isString: jest
98
+ .fn()
99
+ .mockImplementation(
100
+ (value) => Object.prototype.toString.call(value) === '[object String]'
101
+ ),
102
+ };
103
+ return new Promise((resolve) => resolve());
104
+ };
105
+ }
106
+
85
107
  function mockSuccessfulHeadlessInitialization() {
86
108
  // @ts-ignore
87
109
  mockHeadlessLoader.initializeWithHeadless = (element, _, initialize) => {
@@ -104,6 +126,7 @@ function cleanup() {
104
126
  describe('c-quantic-category-facet', () => {
105
127
  beforeAll(() => {
106
128
  mockSuccessfulHeadlessInitialization();
129
+ mockBueno();
107
130
  });
108
131
 
109
132
  afterEach(() => {
@@ -198,6 +221,90 @@ describe('c-quantic-category-facet', () => {
198
221
  });
199
222
  });
200
223
 
224
+ describe('validation of the dependsOn property', () => {
225
+ let consoleError;
226
+ beforeAll(() => {
227
+ consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
228
+ });
229
+
230
+ describe('when dependsOn.parentFacetId is not provided', () => {
231
+ it('should display the error component', async () => {
232
+ const invalidFacetDependency = {
233
+ expectedValue: 'txt',
234
+ };
235
+ const element = createTestComponent({
236
+ ...defaultOptions,
237
+ dependsOn: invalidFacetDependency,
238
+ });
239
+ await flushPromises();
240
+
241
+ const componentError = element.shadowRoot.querySelector(
242
+ selectors.componentError
243
+ );
244
+ const facetContent = element.shadowRoot.querySelector(
245
+ selectors.facetContent
246
+ );
247
+
248
+ expect(consoleError).toHaveBeenCalledTimes(1);
249
+ expect(consoleError).toHaveBeenCalledWith(parentFacetIdError);
250
+ expect(componentError).not.toBeNull();
251
+ expect(facetContent).toBeNull();
252
+ });
253
+ });
254
+
255
+ describe('when dependsOn.parentFacetId is not a string', () => {
256
+ it('should display the error component', async () => {
257
+ const invalidFacetDependency = {
258
+ parentFacetId: 1,
259
+ expectedValue: 'txt',
260
+ };
261
+ const element = createTestComponent({
262
+ ...defaultOptions,
263
+ dependsOn: invalidFacetDependency,
264
+ });
265
+ await flushPromises();
266
+
267
+ const componentError = element.shadowRoot.querySelector(
268
+ selectors.componentError
269
+ );
270
+ const facetContent = element.shadowRoot.querySelector(
271
+ selectors.facetContent
272
+ );
273
+
274
+ expect(consoleError).toHaveBeenCalledTimes(1);
275
+ expect(consoleError).toHaveBeenCalledWith(parentFacetIdError);
276
+ expect(componentError).not.toBeNull();
277
+ expect(facetContent).toBeNull();
278
+ });
279
+ });
280
+
281
+ describe('when dependsOn.expectedValue is not a string', () => {
282
+ it('should display the error component', async () => {
283
+ const invalidFacetDependency = {
284
+ parentFacetId: 'filetype',
285
+ expectedValue: 2,
286
+ };
287
+ const element = createTestComponent({
288
+ ...defaultOptions,
289
+ dependsOn: invalidFacetDependency,
290
+ });
291
+ await flushPromises();
292
+
293
+ const componentError = element.shadowRoot.querySelector(
294
+ selectors.componentError
295
+ );
296
+ const facetContent = element.shadowRoot.querySelector(
297
+ selectors.facetContent
298
+ );
299
+
300
+ expect(consoleError).toHaveBeenCalledTimes(1);
301
+ expect(consoleError).toHaveBeenCalledWith(expectedValueError);
302
+ expect(componentError).not.toBeNull();
303
+ expect(facetContent).toBeNull();
304
+ });
305
+ });
306
+ });
307
+
201
308
  describe('when the component is disconnected', () => {
202
309
  it('should make the condition manager stop watching the facet', async () => {
203
310
  const exampleFacetDependency = {
@@ -14,6 +14,7 @@ import {
14
14
  initializeWithHeadless,
15
15
  registerToStore,
16
16
  getHeadlessBundle,
17
+ getBueno,
17
18
  } from 'c/quanticHeadlessLoader';
18
19
  import {
19
20
  I18nUtils,
@@ -301,6 +302,7 @@ export default class QuanticCategoryFacet extends LightningElement {
301
302
  },
302
303
  });
303
304
  if (this.dependsOn) {
305
+ this.validateDependsOnProperty();
304
306
  this.initFacetConditionManager(engine);
305
307
  }
306
308
  this.unsubscribe = this.facet.subscribe(() => this.updateState());
@@ -330,6 +332,26 @@ export default class QuanticCategoryFacet extends LightningElement {
330
332
  this.dispatchEvent(renderFacetEvent);
331
333
  }
332
334
 
335
+ validateDependsOnProperty() {
336
+ if (this.dependsOn) {
337
+ getBueno(this).then(() => {
338
+ const {parentFacetId, expectedValue} = this.dependsOn;
339
+ if (!Bueno.isString(parentFacetId)) {
340
+ console.error(
341
+ `The ${this.field} ${this.template.host.localName} requires dependsOn.parentFacetId to be a valid string.`
342
+ );
343
+ this.setInitializationError();
344
+ }
345
+ if (expectedValue && !Bueno.isString(expectedValue)) {
346
+ console.error(
347
+ `The ${this.field} ${this.template.host.localName} requires dependsOn.expectedValue to be a valid string.`
348
+ );
349
+ this.setInitializationError();
350
+ }
351
+ });
352
+ }
353
+ }
354
+
333
355
  initFacetConditionManager(engine) {
334
356
  this.categoryFacetConditionsManager =
335
357
  this.headless.buildFacetConditionsManager(engine, {
@@ -12,16 +12,21 @@ jest.mock('c/quanticUtils', () => ({
12
12
  DATEFACETS: 'dateFacets',
13
13
  },
14
14
  },
15
+ I18nUtils: {
16
+ format: jest.fn(),
17
+ },
15
18
  }));
16
19
  jest.mock('c/quanticHeadlessLoader');
17
20
 
18
21
  const selectors = {
19
22
  facetContent: '[data-test="facet-content"]',
23
+ componentError: 'c-quantic-component-error',
20
24
  };
21
25
 
22
26
  const exampleFacetId = 'example facet id';
27
+ const exampleField = 'exampleField';
23
28
  const defaultOptions = {
24
- field: 'example field',
29
+ field: exampleField,
25
30
  };
26
31
  const dateFacetControllerMock = {
27
32
  subscribe: jest.fn((callback) => callback()),
@@ -30,6 +35,8 @@ const dateFacetControllerMock = {
30
35
  values: [],
31
36
  },
32
37
  };
38
+ const parentFacetIdError = `The ${exampleField} c-quantic-date-facet requires dependsOn.parentFacetId to be a valid string.`;
39
+ const expectedValueError = `The ${exampleField} c-quantic-date-facet requires dependsOn.expectedValue to be a valid string.`;
33
40
 
34
41
  function createTestComponent(options = defaultOptions) {
35
42
  prepareHeadlessState();
@@ -79,6 +86,21 @@ const exampleEngine = {
79
86
  };
80
87
  let isInitialized = false;
81
88
 
89
+ function mockBueno() {
90
+ // @ts-ignore
91
+ mockHeadlessLoader.getBueno = () => {
92
+ // @ts-ignore
93
+ global.Bueno = {
94
+ isString: jest
95
+ .fn()
96
+ .mockImplementation(
97
+ (value) => Object.prototype.toString.call(value) === '[object String]'
98
+ ),
99
+ };
100
+ return new Promise((resolve) => resolve());
101
+ };
102
+ }
103
+
82
104
  function mockSuccessfulHeadlessInitialization() {
83
105
  // @ts-ignore
84
106
  mockHeadlessLoader.initializeWithHeadless = (element, _, initialize) => {
@@ -101,6 +123,7 @@ function cleanup() {
101
123
  describe('c-quantic-date-facet', () => {
102
124
  beforeAll(() => {
103
125
  mockSuccessfulHeadlessInitialization();
126
+ mockBueno();
104
127
  });
105
128
 
106
129
  afterEach(() => {
@@ -193,6 +216,89 @@ describe('c-quantic-date-facet', () => {
193
216
  });
194
217
  });
195
218
 
219
+ describe('validation of the dependsOn property', () => {
220
+ let consoleError;
221
+ beforeAll(() => {
222
+ consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
223
+ });
224
+ describe('when dependsOn.parentFacetId is not provided', () => {
225
+ it('should display the error component', async () => {
226
+ const invalidFacetDependency = {
227
+ expectedValue: 'txt',
228
+ };
229
+ const element = createTestComponent({
230
+ ...defaultOptions,
231
+ dependsOn: invalidFacetDependency,
232
+ });
233
+ await flushPromises();
234
+
235
+ const componentError = element.shadowRoot.querySelector(
236
+ selectors.componentError
237
+ );
238
+ const facetContent = element.shadowRoot.querySelector(
239
+ selectors.facetContent
240
+ );
241
+
242
+ expect(consoleError).toHaveBeenCalledTimes(1);
243
+ expect(consoleError).toHaveBeenCalledWith(parentFacetIdError);
244
+ expect(componentError).not.toBeNull();
245
+ expect(facetContent).toBeNull();
246
+ });
247
+ });
248
+
249
+ describe('when dependsOn.parentFacetId is not a string', () => {
250
+ it('should display the error component', async () => {
251
+ const invalidFacetDependency = {
252
+ parentFacetId: 1,
253
+ expectedValue: 'txt',
254
+ };
255
+ const element = createTestComponent({
256
+ ...defaultOptions,
257
+ dependsOn: invalidFacetDependency,
258
+ });
259
+ await flushPromises();
260
+
261
+ const componentError = element.shadowRoot.querySelector(
262
+ selectors.componentError
263
+ );
264
+ const facetContent = element.shadowRoot.querySelector(
265
+ selectors.facetContent
266
+ );
267
+
268
+ expect(consoleError).toHaveBeenCalledTimes(1);
269
+ expect(consoleError).toHaveBeenCalledWith(parentFacetIdError);
270
+ expect(componentError).not.toBeNull();
271
+ expect(facetContent).toBeNull();
272
+ });
273
+ });
274
+
275
+ describe('when dependsOn.expectedValue is not a string', () => {
276
+ it('should display the error component', async () => {
277
+ const invalidFacetDependency = {
278
+ parentFacetId: 'filetype',
279
+ expectedValue: 2,
280
+ };
281
+ const element = createTestComponent({
282
+ ...defaultOptions,
283
+ dependsOn: invalidFacetDependency,
284
+ });
285
+ await flushPromises();
286
+
287
+ const componentError = element.shadowRoot.querySelector(
288
+ selectors.componentError
289
+ );
290
+ const facetContent = element.shadowRoot.querySelector(
291
+ selectors.facetContent
292
+ );
293
+
294
+ expect(consoleError).toHaveBeenCalledTimes(1);
295
+ expect(consoleError).toHaveBeenCalledWith(expectedValueError);
296
+ expect(componentError).not.toBeNull();
297
+ expect(facetContent).toBeNull();
298
+ });
299
+ });
300
+ });
301
+
196
302
  describe('when the component is disconnected', () => {
197
303
  it('should make the condition manager stop watching the facet', async () => {
198
304
  const exampleFacetDependency = {
@@ -9,6 +9,7 @@ import {
9
9
  initializeWithHeadless,
10
10
  registerToStore,
11
11
  getHeadlessBundle,
12
+ getBueno,
12
13
  } from 'c/quanticHeadlessLoader';
13
14
  import {
14
15
  I18nUtils,
@@ -210,6 +211,7 @@ export default class QuanticDateFacet extends LightningElement {
210
211
  element: this.template.host,
211
212
  });
212
213
  if (this.dependsOn) {
214
+ this.validateDependsOnProperty();
213
215
  this.initFacetConditionManager(engine);
214
216
  }
215
217
  };
@@ -238,6 +240,26 @@ export default class QuanticDateFacet extends LightningElement {
238
240
  this.dispatchEvent(renderFacetEvent);
239
241
  }
240
242
 
243
+ validateDependsOnProperty() {
244
+ if (this.dependsOn) {
245
+ getBueno(this).then(() => {
246
+ const {parentFacetId, expectedValue} = this.dependsOn;
247
+ if (!Bueno.isString(parentFacetId)) {
248
+ console.error(
249
+ `The ${this.field} ${this.template.host.localName} requires dependsOn.parentFacetId to be a valid string.`
250
+ );
251
+ this.setInitializationError();
252
+ }
253
+ if (expectedValue && !Bueno.isString(expectedValue)) {
254
+ console.error(
255
+ `The ${this.field} ${this.template.host.localName} requires dependsOn.expectedValue to be a valid string.`
256
+ );
257
+ this.setInitializationError();
258
+ }
259
+ });
260
+ }
261
+ }
262
+
241
263
  initFacetConditionManager(engine) {
242
264
  this.dateFacetConditionsManager = this.headless.buildFacetConditionsManager(
243
265
  engine,
@@ -12,16 +12,21 @@ jest.mock('c/quanticUtils', () => ({
12
12
  FACETS: 'facets',
13
13
  },
14
14
  },
15
+ I18nUtils: {
16
+ format: jest.fn(),
17
+ },
15
18
  }));
16
19
  jest.mock('c/quanticHeadlessLoader');
17
20
 
18
21
  const selectors = {
19
22
  facetContent: '[data-test="facet-content"]',
23
+ componentError: 'c-quantic-component-error',
20
24
  };
21
25
 
22
26
  const exampleFacetId = 'example facet id';
27
+ const exampleField = 'exampleField';
23
28
  const defaultOptions = {
24
- field: 'example field',
29
+ field: exampleField,
25
30
  };
26
31
  const facetControllerMock = {
27
32
  subscribe: jest.fn((callback) => callback()),
@@ -30,6 +35,8 @@ const facetControllerMock = {
30
35
  values: [],
31
36
  },
32
37
  };
38
+ const parentFacetIdError = `The ${exampleField} c-quantic-facet requires dependsOn.parentFacetId to be a valid string.`;
39
+ const expectedValueError = `The ${exampleField} c-quantic-facet requires dependsOn.expectedValue to be a valid string.`;
33
40
 
34
41
  function createTestComponent(options = defaultOptions) {
35
42
  prepareHeadlessState();
@@ -89,6 +96,21 @@ function mockSuccessfulHeadlessInitialization() {
89
96
  };
90
97
  }
91
98
 
99
+ function mockBueno() {
100
+ // @ts-ignore
101
+ mockHeadlessLoader.getBueno = () => {
102
+ // @ts-ignore
103
+ global.Bueno = {
104
+ isString: jest
105
+ .fn()
106
+ .mockImplementation(
107
+ (value) => Object.prototype.toString.call(value) === '[object String]'
108
+ ),
109
+ };
110
+ return new Promise((resolve) => resolve());
111
+ };
112
+ }
113
+
92
114
  function cleanup() {
93
115
  // The jsdom instance is shared across test cases in a single file so reset the DOM
94
116
  while (document.body.firstChild) {
@@ -101,6 +123,7 @@ function cleanup() {
101
123
  describe('c-quantic-facet', () => {
102
124
  beforeAll(() => {
103
125
  mockSuccessfulHeadlessInitialization();
126
+ mockBueno();
104
127
  });
105
128
 
106
129
  afterEach(() => {
@@ -214,6 +237,88 @@ describe('c-quantic-facet', () => {
214
237
  });
215
238
  });
216
239
 
240
+ describe('validation of the dependsOn property', () => {
241
+ let consoleError;
242
+ beforeAll(() => {
243
+ consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
244
+ });
245
+ describe('when dependsOn.parentFacetId is not provided', () => {
246
+ it('should display the error component', async () => {
247
+ const invalidFacetDependency = {
248
+ expectedValue: 'txt',
249
+ };
250
+ const element = createTestComponent({
251
+ ...defaultOptions,
252
+ dependsOn: invalidFacetDependency,
253
+ });
254
+ await flushPromises();
255
+
256
+ const componentError = element.shadowRoot.querySelector(
257
+ selectors.componentError
258
+ );
259
+ const facetContent = element.shadowRoot.querySelector(
260
+ selectors.facetContent
261
+ );
262
+
263
+ expect(consoleError).toHaveBeenCalledTimes(1);
264
+ expect(consoleError).toHaveBeenCalledWith(parentFacetIdError);
265
+ expect(componentError).not.toBeNull();
266
+ expect(facetContent).toBeNull();
267
+ });
268
+ });
269
+
270
+ describe('when dependsOn.parentFacetId is not a string', () => {
271
+ it('should display the error component', async () => {
272
+ const invalidFacetDependency = {
273
+ parentFacetId: 1,
274
+ expectedValue: 'txt',
275
+ };
276
+ const element = createTestComponent({
277
+ ...defaultOptions,
278
+ dependsOn: invalidFacetDependency,
279
+ });
280
+ await flushPromises();
281
+
282
+ const componentError = element.shadowRoot.querySelector(
283
+ selectors.componentError
284
+ );
285
+ const facetContent = element.shadowRoot.querySelector(
286
+ selectors.facetContent
287
+ );
288
+
289
+ expect(consoleError).toHaveBeenCalledTimes(1);
290
+ expect(consoleError).toHaveBeenCalledWith(parentFacetIdError);
291
+ expect(componentError).not.toBeNull();
292
+ expect(facetContent).toBeNull();
293
+ });
294
+ });
295
+
296
+ describe('when dependsOn.expectedValue is not a string', () => {
297
+ it('should display the error component', async () => {
298
+ const invalidFacetDependency = {
299
+ parentFacetId: 'filetype',
300
+ expectedValue: 2,
301
+ };
302
+ const element = createTestComponent({
303
+ ...defaultOptions,
304
+ dependsOn: invalidFacetDependency,
305
+ });
306
+ await flushPromises();
307
+
308
+ const componentError = element.shadowRoot.querySelector(
309
+ selectors.componentError
310
+ );
311
+ const facetContent = element.shadowRoot.querySelector(
312
+ selectors.facetContent
313
+ );
314
+
315
+ expect(consoleError).toHaveBeenCalledTimes(1);
316
+ expect(consoleError).toHaveBeenCalledWith(expectedValueError);
317
+ expect(componentError).not.toBeNull();
318
+ expect(facetContent).toBeNull();
319
+ });
320
+ });
321
+ });
217
322
  describe('when the component is disconnected', () => {
218
323
  it('should make the condition manager stop watching the facet', async () => {
219
324
  const exampleFacetDependency = {