@coveo/quantic 3.37.10 → 3.38.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.
Files changed (34) hide show
  1. package/force-app/main/default/lwc/quanticFeedback/__tests__/quanticFeedback.test.js +11 -0
  2. package/force-app/main/default/lwc/quanticFeedback/quanticFeedback.html +6 -4
  3. package/force-app/main/default/lwc/quanticFeedback/quanticFeedback.js +8 -0
  4. package/force-app/main/default/lwc/quanticGeneratedAnswer/__tests__/quanticGeneratedAnswer.test.js +32 -3
  5. package/force-app/main/default/lwc/quanticGeneratedAnswer/quanticGeneratedAnswer.css +1 -8
  6. package/force-app/main/default/lwc/quanticGeneratedAnswer/quanticGeneratedAnswer.js +6 -28
  7. package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/generatedAnswer.css +0 -13
  8. package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/generatedAnswer.html +120 -93
  9. package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/retryPrompt.html +24 -15
  10. package/force-app/main/default/lwc/quanticGeneratedAnswerCopyToClipboard/__tests__/quanticGeneratedAnswerCopyToClipboard.test.js +125 -0
  11. package/force-app/main/default/lwc/quanticGeneratedAnswerCopyToClipboard/quanticGeneratedAnswerCopyToClipboard.html +1 -1
  12. package/force-app/main/default/lwc/quanticGeneratedAnswerCopyToClipboard/quanticGeneratedAnswerCopyToClipboard.js +20 -1
  13. package/force-app/main/default/lwc/quanticSort/e2e/pageObject.ts +29 -18
  14. package/force-app/main/default/lwc/quanticSort/e2e/quanticSort.e2e.ts +6 -8
  15. package/force-app/main/default/lwc/quanticThreadItem/__tests__/quanticThreadItem.test.js +176 -0
  16. package/force-app/main/default/lwc/quanticThreadItem/quanticThreadItem.css +64 -0
  17. package/force-app/main/default/lwc/quanticThreadItem/quanticThreadItem.html +48 -0
  18. package/force-app/main/default/lwc/quanticThreadItem/quanticThreadItem.js +80 -0
  19. package/force-app/main/default/lwc/quanticThreadItem/quanticThreadItem.js-meta.xml +5 -0
  20. package/force-app/main/default/staticresources/coveoheadless/case-assist/headless.js +15 -15
  21. package/force-app/main/default/staticresources/coveoheadless/definitions/app/case-assist-engine/case-assist-engine-configuration.d.ts +1 -1
  22. package/force-app/main/default/staticresources/coveoheadless/definitions/app/insight-engine/insight-engine-configuration.d.ts +1 -1
  23. package/force-app/main/default/staticresources/coveoheadless/definitions/app/recommendation-engine/recommendation-engine-configuration.d.ts +1 -1
  24. package/force-app/main/default/staticresources/coveoheadless/definitions/app/search-engine/search-engine-configuration.d.ts +1 -1
  25. package/force-app/main/default/staticresources/coveoheadless/definitions/features/case-assist-configuration/case-assist-configuration-actions.d.ts +1 -1
  26. package/force-app/main/default/staticresources/coveoheadless/definitions/features/case-assist-configuration/case-assist-configuration-state.d.ts +1 -1
  27. package/force-app/main/default/staticresources/coveoheadless/definitions/features/configuration/configuration-actions.d.ts +1 -1
  28. package/force-app/main/default/staticresources/coveoheadless/definitions/features/configuration/configuration-state.d.ts +1 -1
  29. package/force-app/main/default/staticresources/coveoheadless/definitions/features/generated-answer/generated-answer-request.d.ts +2 -2
  30. package/force-app/main/default/staticresources/coveoheadless/headless.js +17 -17
  31. package/force-app/main/default/staticresources/coveoheadless/insight/headless.js +16 -16
  32. package/force-app/main/default/staticresources/coveoheadless/recommendation/headless.js +14 -14
  33. package/force-app/main/default/staticresources/dompurify/purify.min.js +2 -2
  34. package/package.json +4 -4
@@ -12,8 +12,27 @@ import {LightningElement, api} from 'lwc';
12
12
  export default class QuanticGeneratedAnswerCopyToClipboard extends LightningElement {
13
13
  /**
14
14
  * The answer to copy
15
+ * @type {string}
15
16
  */
16
- @api answer;
17
+ @api answer = '';
18
+
19
+ /**
20
+ * The size of the copy icon.
21
+ * @api
22
+ * @type {'xx-small' | 'x-small' | 'small' | 'medium' | 'large'}
23
+ */
24
+ @api
25
+ get size() {
26
+ return this._size;
27
+ }
28
+ set size(value) {
29
+ if (['xx-small', 'x-small', 'small', 'medium', 'large'].includes(value)) {
30
+ this._size = value;
31
+ }
32
+ }
33
+
34
+ /** @type {'xx-small' | 'x-small' | 'small' | 'medium' | 'large'} */
35
+ _size = 'xx-small';
17
36
 
18
37
  labels = {
19
38
  copy,
@@ -1,4 +1,4 @@
1
- import type {Locator, Page, Request} from '@playwright/test';
1
+ import {expect, type Locator, type Page, type Request} from '@playwright/test';
2
2
  import {isUaSearchEvent} from '../../../../../../playwright/utils/requests';
3
3
 
4
4
  export class SortObject {
@@ -32,7 +32,20 @@ export class SortObject {
32
32
  } else {
33
33
  await this.sortDropDown.press('Space');
34
34
  }
35
- await this.sortButton('Oldest').waitFor({state: 'visible'});
35
+ const overlay = this.page.locator('div[part="dropdown overlay"]');
36
+ await overlay.waitFor({state: 'visible'});
37
+ await overlay
38
+ .locator('[role="option"]')
39
+ .first()
40
+ .waitFor({state: 'visible'});
41
+ }
42
+
43
+ async selectNextSortOptionUsingKeyboard(
44
+ expectedLabel: string
45
+ ): Promise<void> {
46
+ await this.sortDropDown.press('ArrowDown');
47
+ await this.sortDropDown.press('Enter');
48
+ await expect(this.sortDropDown).toHaveText(expectedLabel);
36
49
  }
37
50
 
38
51
  async waitForSortUaAnalytics(eventValue: any): Promise<Request> {
@@ -60,24 +73,22 @@ export class SortObject {
60
73
  return uaRequest;
61
74
  }
62
75
 
63
- async allSortLabelOptions() {
64
- const options = await this.page
65
- .locator('div[part="dropdown overlay"]')
66
- .allInnerTexts();
67
- const arrSort = options[0].split('\n');
68
- return arrSort;
69
- }
76
+ async getSortLabelValue(): Promise<{label: string; value: string | null}[]> {
77
+ const sortOptions = this.page.locator(
78
+ 'div[part="dropdown overlay"] [role="option"]'
79
+ );
80
+ await sortOptions.first().waitFor({state: 'visible'});
70
81
 
71
- async getSortLabelValue() {
72
- const arrSort = await this.allSortLabelOptions();
73
- const sortLabelwithValueList = await Promise.all(
74
- arrSort.map(async (item) => ({
75
- label: item,
76
- value: await this.page
77
- .getByRole('option', {name: item})
78
- .getAttribute('data-value'),
82
+ return sortOptions.evaluateAll((options) =>
83
+ options.map((option) => ({
84
+ label: (
85
+ (option as HTMLElement).innerText ||
86
+ option.getAttribute('aria-label') ||
87
+ option.getAttribute('title') ||
88
+ ''
89
+ ).trim(),
90
+ value: option.getAttribute('data-value'),
79
91
  }))
80
92
  );
81
- return sortLabelwithValueList;
82
93
  }
83
94
  }
@@ -11,8 +11,8 @@ const fixtures = {
11
11
 
12
12
  useCaseTestCases.forEach((useCase) => {
13
13
  let test;
14
- let sortArrOptions;
15
- let sortLabelWithValue;
14
+ let sortArrOptions: string[];
15
+ let sortLabelWithValue: {label: string; value: string | null}[];
16
16
  if (useCase.value === useCaseEnum.search) {
17
17
  test = fixtures[useCase.value] as typeof testSearch;
18
18
  } else {
@@ -22,8 +22,8 @@ useCaseTestCases.forEach((useCase) => {
22
22
  test.describe(`quantic sort ${useCase.label}`, () => {
23
23
  test.beforeEach(async ({sort}) => {
24
24
  await sort.clickSortDropDown();
25
- sortArrOptions = await sort.allSortLabelOptions();
26
25
  sortLabelWithValue = await sort.getSortLabelValue();
26
+ sortArrOptions = sortLabelWithValue.map(({label}) => label);
27
27
  await sort.clickSortDropDown();
28
28
  });
29
29
 
@@ -53,15 +53,13 @@ useCaseTestCases.forEach((useCase) => {
53
53
  // Selecting the next sort using Enter to open dropdown, the ArrowDown, then ENTER key
54
54
  await sort.focusSortDropDown();
55
55
  await sort.openSortDropdownUsingKeyboardEnter();
56
- await sort.sortDropDown.press('ArrowDown');
57
- await sort.sortDropDown.press('Enter');
56
+ await sort.selectNextSortOptionUsingKeyboard(sortArrOptions[1]);
58
57
  await expect(sort.sortDropDown).toHaveText(sortArrOptions[1]);
59
58
 
60
59
  // Selecting the next sort using Space to open dropdown, the ArrowDown, then ENTER key
61
60
  await sort.focusSortDropDown();
62
61
  await sort.openSortDropdownUsingKeyboardEnter(false);
63
- await sort.sortDropDown.press('ArrowDown');
64
- await sort.sortDropDown.press('Enter');
62
+ await sort.selectNextSortOptionUsingKeyboard(sortArrOptions[2]);
65
63
  await expect(sort.sortDropDown).toHaveText(sortArrOptions[2]);
66
64
  });
67
65
  });
@@ -76,7 +74,7 @@ useCaseTestCases.forEach((useCase) => {
76
74
  sortLabelWithValue[2]; // Assuming 0 is the first sort option
77
75
 
78
76
  const currentUrl = await page.url();
79
- const urlHash = `#sortCriteria=${encodeURI(expectedSortValue)}`;
77
+ const urlHash = `#sortCriteria=${encodeURI(expectedSortValue!)}`;
80
78
 
81
79
  await page.goto(`${currentUrl}/${urlHash}`);
82
80
  await page.getByRole('button', {name: 'Try it now'}).click();
@@ -0,0 +1,176 @@
1
+ // @ts-ignore
2
+ import QuanticThreadItem from '../quanticThreadItem';
3
+ import {buildCreateTestComponent, cleanup, flushPromises} from 'c/testUtils';
4
+
5
+ const selectors = {
6
+ titleButton: '[data-testid="thread-item-title-button"]',
7
+ titleSpan: '[data-testid="thread-item-title-static"]',
8
+ boldTitle: '.slds-text-title_bold',
9
+ contentWrapper: '[data-testid="thread-item-content"]',
10
+ visibleContent: '[data-testid="thread-item-content"] > div:not([hidden])',
11
+ line: '[data-testid="thread-item-line"]',
12
+ dot: '[data-testid="thread-item-dot"]',
13
+ };
14
+
15
+ const createTestComponent = buildCreateTestComponent(
16
+ QuanticThreadItem,
17
+ 'c-quantic-thread-item',
18
+ {
19
+ title: 'Test title',
20
+ }
21
+ );
22
+
23
+ describe('c-quantic-thread-item', () => {
24
+ afterEach(() => {
25
+ cleanup();
26
+ });
27
+
28
+ describe('initial rendering', () => {
29
+ it('renders a button when collapse is enabled', async () => {
30
+ const element = createTestComponent();
31
+ await flushPromises();
32
+
33
+ const button = element.shadowRoot.querySelector(selectors.titleButton);
34
+ expect(button).not.toBeNull();
35
+ });
36
+
37
+ it('renders a span instead of a button when disableCollapse is true', async () => {
38
+ const element = createTestComponent({disableCollapse: true});
39
+ await flushPromises();
40
+
41
+ const button = element.shadowRoot.querySelector(selectors.titleButton);
42
+ const span = element.shadowRoot.querySelector(selectors.titleSpan);
43
+ expect(button).toBeNull();
44
+ expect(span).not.toBeNull();
45
+ });
46
+
47
+ it('renders the timeline line by default', async () => {
48
+ const element = createTestComponent();
49
+ await flushPromises();
50
+
51
+ const line = element.shadowRoot.querySelector(selectors.line);
52
+ expect(line).not.toBeNull();
53
+ });
54
+
55
+ it('hides the timeline line when hideLine is true', async () => {
56
+ const element = createTestComponent({hideLine: true});
57
+ await flushPromises();
58
+
59
+ const line = element.shadowRoot.querySelector(selectors.line);
60
+ expect(line).toBeNull();
61
+ });
62
+ });
63
+
64
+ describe('collapsed state', () => {
65
+ it('does not render the content by default', async () => {
66
+ const element = createTestComponent();
67
+ await flushPromises();
68
+
69
+ const content = element.shadowRoot.querySelector(
70
+ selectors.visibleContent
71
+ );
72
+ expect(content).toBeNull();
73
+ });
74
+
75
+ it('button has aria-expanded set to false when collapsed', async () => {
76
+ const element = createTestComponent();
77
+ await flushPromises();
78
+
79
+ const button = element.shadowRoot.querySelector(selectors.titleButton);
80
+ expect(button.getAttribute('aria-expanded')).toBe('false');
81
+ });
82
+
83
+ it('dot does not have expanded class when collapsed', async () => {
84
+ const element = createTestComponent();
85
+ await flushPromises();
86
+
87
+ const dot = element.shadowRoot.querySelector(selectors.dot);
88
+ expect(dot.className).not.toContain('thread-item__dot--expanded');
89
+ });
90
+ });
91
+
92
+ describe('expanded state', () => {
93
+ it('renders the content when isExpanded is true', async () => {
94
+ const element = createTestComponent({isExpanded: true});
95
+ await flushPromises();
96
+
97
+ const content = element.shadowRoot.querySelector(
98
+ selectors.visibleContent
99
+ );
100
+ expect(content).not.toBeNull();
101
+ });
102
+
103
+ it('button has aria-expanded set to true when expanded', async () => {
104
+ const element = createTestComponent({isExpanded: true});
105
+ await flushPromises();
106
+
107
+ const button = element.shadowRoot.querySelector(selectors.titleButton);
108
+ expect(button.getAttribute('aria-expanded')).toBe('true');
109
+ });
110
+
111
+ it('dot has expanded class when expanded', async () => {
112
+ const element = createTestComponent({isExpanded: true});
113
+ await flushPromises();
114
+
115
+ const dot = element.shadowRoot.querySelector(selectors.dot);
116
+ expect(dot.className).toContain('thread-item__dot--expanded');
117
+ });
118
+ });
119
+
120
+ describe('toggle interaction', () => {
121
+ it('renders the content when the button is clicked while collapsed', async () => {
122
+ const element = createTestComponent();
123
+ await flushPromises();
124
+
125
+ element.shadowRoot.querySelector(selectors.titleButton).click();
126
+ await flushPromises();
127
+
128
+ const content = element.shadowRoot.querySelector(
129
+ selectors.visibleContent
130
+ );
131
+ expect(content).not.toBeNull();
132
+ });
133
+
134
+ it('does not render the content when the button is clicked while expanded', async () => {
135
+ const element = createTestComponent({isExpanded: true});
136
+ await flushPromises();
137
+
138
+ element.shadowRoot.querySelector(selectors.titleButton).click();
139
+ await flushPromises();
140
+
141
+ const content = element.shadowRoot.querySelector(
142
+ selectors.visibleContent
143
+ );
144
+ expect(content).toBeNull();
145
+ });
146
+ });
147
+
148
+ describe('disableCollapse', () => {
149
+ it('forces item to be expanded regardless of isExpanded prop', async () => {
150
+ const element = createTestComponent({
151
+ disableCollapse: true,
152
+ isExpanded: false,
153
+ });
154
+ await flushPromises();
155
+
156
+ const content = element.shadowRoot.querySelector(
157
+ selectors.visibleContent
158
+ );
159
+ expect(content).not.toBeNull();
160
+ });
161
+
162
+ it('renders a bold title when collapse is disabled', async () => {
163
+ const element = createTestComponent({
164
+ disableCollapse: true,
165
+ isExpanded: false,
166
+ });
167
+ await flushPromises();
168
+
169
+ const title = element.shadowRoot.querySelector(selectors.titleSpan);
170
+ expect(title.className).toContain('slds-text-title_bold');
171
+ expect(
172
+ element.shadowRoot.querySelector(selectors.boldTitle)
173
+ ).not.toBeNull();
174
+ });
175
+ });
176
+ });
@@ -0,0 +1,64 @@
1
+ .thread-item {
2
+ list-style: none;
3
+ }
4
+
5
+ .thread-item__dot-column,
6
+ .thread-item__line-column {
7
+ width: 10px;
8
+ }
9
+
10
+ .thread-item__line-column {
11
+ align-items: stretch;
12
+ }
13
+
14
+ .thread-item__dot {
15
+ height: 8px;
16
+ width: 8px;
17
+ border-radius: 50%;
18
+ background-color: var(--lwc-colorBorder, #dddbda);
19
+ }
20
+
21
+ .thread-item__dot--expanded {
22
+ background-color: var(--lwc-colorTextDefault, #080707);
23
+ }
24
+
25
+ .thread-item__line {
26
+ position: relative;
27
+ width: 1px;
28
+ height: 100%;
29
+ background-color: var(--lwc-colorBorder, #dddbda);
30
+ }
31
+
32
+ .thread-item__line::before,
33
+ .thread-item__line::after {
34
+ content: '';
35
+ position: absolute;
36
+ left: 0;
37
+ width: 1px;
38
+ height: 8px;
39
+ background-color: var(--lwc-colorBorder, #dddbda);
40
+ }
41
+
42
+ .thread-item__line::before {
43
+ top: -8px;
44
+ }
45
+
46
+ .thread-item__line::after {
47
+ bottom: -8px;
48
+ }
49
+
50
+ .thread-item__clamped-text {
51
+ display: -webkit-box;
52
+ -webkit-line-clamp: 3;
53
+ -webkit-box-orient: vertical;
54
+ overflow: hidden;
55
+ }
56
+
57
+ .thread-item__title-button {
58
+ width: fit-content;
59
+ border-radius: var(--slds-g-radius-border-2, 4px);
60
+ }
61
+
62
+ .thread-item__title-button:hover {
63
+ background-color: var(--lwc-colorBackgroundRowHover, #f3f2f2);
64
+ }
@@ -0,0 +1,48 @@
1
+ <template>
2
+ <li class="thread-item">
3
+ <div class="slds-grid slds-grid_vertical-align-center">
4
+ <div
5
+ class="slds-grid slds-grid_align-center slds-grid_vertical-align-center slds-shrink-none slds-m-right_x-small thread-item__dot-column"
6
+ >
7
+ <span class={dotClass} data-testid="thread-item-dot"></span>
8
+ </div>
9
+ <div class="slds-col slds-has-flexi-truncate">
10
+ <template lwc:if={disableCollapse}>
11
+ <span class={titleClass} data-testid="thread-item-title-static"
12
+ >{title}</span
13
+ >
14
+ </template>
15
+ <template lwc:else>
16
+ <button
17
+ type="button"
18
+ class={titleButtonClass}
19
+ aria-expanded={isExpanded}
20
+ data-testid="thread-item-title-button"
21
+ onclick={handleTitleClick}
22
+ aria-controls="thread-item-content"
23
+ >
24
+ {title}
25
+ </button>
26
+ </template>
27
+ </div>
28
+ </div>
29
+ <div class="slds-grid">
30
+ <div
31
+ class="slds-grid slds-grid_align-center slds-shrink-none thread-item__line-column slds-m-right_x-small"
32
+ >
33
+ <template lwc:if={shouldDisplayLine}>
34
+ <span class="thread-item__line" data-testid="thread-item-line"></span>
35
+ </template>
36
+ </div>
37
+ <div
38
+ id="thread-item-content"
39
+ class="slds-col slds-p-left_x-small slds-p-vertical_xx-small"
40
+ data-testid="thread-item-content"
41
+ >
42
+ <div hidden={contentHidden}>
43
+ <slot></slot>
44
+ </div>
45
+ </div>
46
+ </div>
47
+ </li>
48
+ </template>
@@ -0,0 +1,80 @@
1
+ import {LightningElement, api} from 'lwc';
2
+
3
+ /**
4
+ * The `QuanticThreadItem` component renders a thread item with timeline visuals and collapsible content.
5
+ * @category Internal
6
+ * @example
7
+ * <c-quantic-thread-item title="Step title" hide-line is-expanded></c-quantic-thread-item>
8
+ */
9
+ export default class QuanticThreadItem extends LightningElement {
10
+ /**
11
+ * The title displayed for the thread item.
12
+ * @api
13
+ * @type {string}
14
+ */
15
+ @api title = '';
16
+
17
+ /**
18
+ * Whether the thread item can be expanded or collapsed.
19
+ * @api
20
+ * @type {boolean}
21
+ * @defaultValue `false`
22
+ */
23
+ @api disableCollapse = false;
24
+
25
+ /**
26
+ * Whether the timeline line should be hidden.
27
+ * @api
28
+ * @type {boolean}
29
+ * @defaultValue `false`
30
+ */
31
+ @api hideLine = false;
32
+
33
+ /**
34
+ * Whether the thread item is expanded.
35
+ * @api
36
+ * @type {boolean}
37
+ * @defaultValue `false`
38
+ */
39
+ @api
40
+ get isExpanded() {
41
+ return this._isExpanded;
42
+ }
43
+ set isExpanded(value) {
44
+ this._isExpanded = value;
45
+ }
46
+
47
+ /** @type {boolean} */
48
+ _isExpanded = false;
49
+
50
+ connectedCallback() {
51
+ this._isExpanded = this.disableCollapse ? true : this.isExpanded;
52
+ }
53
+
54
+ handleTitleClick() {
55
+ if (this.disableCollapse) {
56
+ return;
57
+ }
58
+ this._isExpanded = !this._isExpanded;
59
+ }
60
+
61
+ get shouldDisplayLine() {
62
+ return !this.hideLine;
63
+ }
64
+
65
+ get contentHidden() {
66
+ return !this._isExpanded;
67
+ }
68
+
69
+ get titleClass() {
70
+ return `thread-item__clamped-text slds-text-body_regular slds-p-horizontal_x-small slds-p-vertical_xx-small${this._isExpanded ? ' slds-text-title_bold' : ''}`;
71
+ }
72
+
73
+ get titleButtonClass() {
74
+ return `slds-button_reset slds-p-horizontal_x-small slds-p-vertical_xx-small thread-item__title-button thread-item__clamped-text${this._isExpanded ? ' slds-text-title_bold' : ''}`;
75
+ }
76
+
77
+ get dotClass() {
78
+ return `thread-item__dot${this._isExpanded ? ' thread-item__dot--expanded' : ''}`;
79
+ }
80
+ }
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
3
+ <apiVersion>66.0</apiVersion>
4
+ <isExposed>false</isExposed>
5
+ </LightningComponentBundle>