@coveo/quantic 3.41.0 → 3.42.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/docs/out/quantic-docs.json +9 -0
- package/force-app/main/default/labels/CustomLabels.labels-meta.xml +14 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswer/__tests__/quanticGeneratedAnswer.test.js +271 -22
- package/force-app/main/default/lwc/quanticGeneratedAnswer/quanticGeneratedAnswer.js +92 -31
- package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/conversation.css +7 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/conversation.html +63 -0
- package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/generatedAnswer.css +0 -11
- package/force-app/main/default/lwc/quanticGeneratedAnswer/templates/generatedAnswer.html +2 -2
- package/force-app/main/default/lwc/quanticGeneratedAnswerBody/__tests__/quanticGeneratedAnswerBody.test.js +39 -1
- package/force-app/main/default/lwc/quanticGeneratedAnswerBody/quanticGeneratedAnswerBody.js +8 -4
- package/force-app/main/default/lwc/quanticGeneratedAnswerBody/templates/answer.html +30 -24
- package/force-app/main/default/lwc/quanticGeneratedAnswerCopyToClipboard/__tests__/quanticGeneratedAnswerCopyToClipboard.test.js +5 -1
- package/force-app/main/default/lwc/quanticGeneratedAnswerCopyToClipboard/quanticGeneratedAnswerCopyToClipboard.js +9 -2
- package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/__tests__/quanticGeneratedAnswerFollowUpInput.test.js +109 -55
- package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/quanticGeneratedAnswerFollowUpInput.css +59 -11
- package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/quanticGeneratedAnswerFollowUpInput.html +14 -13
- package/force-app/main/default/lwc/quanticGeneratedAnswerFollowUpInput/quanticGeneratedAnswerFollowUpInput.js +40 -2
- package/force-app/main/default/lwc/quanticSourceCitations/__tests__/quanticSourceCitations.test.js +22 -2
- package/force-app/main/default/lwc/quanticSourceCitations/quanticSourceCitations.js +7 -0
- package/force-app/main/default/staticresources/coveoheadless/case-assist/headless.js +15 -15
- package/force-app/main/default/staticresources/coveoheadless/definitions/commerce.index.d.ts +1 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/core/sub-controller/headless-sub-controller.d.ts +3 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/recent-queries-list/headless-recent-queries-list.d.ts +13 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/commerce/search/did-you-mean/headless-did-you-mean.d.ts +10 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/controllers/generated-answer/headless-generated-answer.d.ts +2 -2
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/follow-up-answers/follow-up-answers-actions.d.ts +32 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/generated-answer/generated-answer-actions.d.ts +26 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/features/generated-answer/generated-answer-state.d.ts +19 -0
- package/force-app/main/default/staticresources/coveoheadless/definitions/index.d.ts +1 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/ssr/commerce/controllers/did-you-mean/headless-did-you-mean.ssr.d.ts +4 -3
- package/force-app/main/default/staticresources/coveoheadless/definitions/ssr-commerce-next.index.d.ts +1 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/ssr-commerce.index.d.ts +1 -1
- package/force-app/main/default/staticresources/coveoheadless/definitions/ssr-next/commerce/controllers/did-you-mean/headless-did-you-mean.ssr.d.ts +4 -3
- package/force-app/main/default/staticresources/coveoheadless/headless.js +18 -18
- package/force-app/main/default/staticresources/coveoheadless/insight/headless.js +16 -16
- package/force-app/main/default/staticresources/coveoheadless/recommendation/headless.js +14 -14
- package/package.json +2 -2
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<template lwc:if={shouldDisplayGeneratedAnswer}>
|
|
3
|
+
<div
|
|
4
|
+
data-testid="generated-answer__card"
|
|
5
|
+
class="slds-box slds-p-around_none"
|
|
6
|
+
>
|
|
7
|
+
<header
|
|
8
|
+
data-testid="generated-answer__header"
|
|
9
|
+
class={generatedAnswerHeaderClass}
|
|
10
|
+
>
|
|
11
|
+
<div
|
|
12
|
+
class="generated-answer__header-title slds-grid slds-grid_vertical-align-center"
|
|
13
|
+
>
|
|
14
|
+
<lightning-icon
|
|
15
|
+
icon-name="utility:sparkles"
|
|
16
|
+
size="x-small"
|
|
17
|
+
alternative-text={labels.generatedAnswerForYou}
|
|
18
|
+
class="slds-current-color slds-var-m-right_x-small"
|
|
19
|
+
></lightning-icon>
|
|
20
|
+
<span
|
|
21
|
+
data-testid="generated-answer__header-title"
|
|
22
|
+
class="slds-text-title_bold slds-p-vertical_x-small"
|
|
23
|
+
>{labels.generatedAnswerForYou}
|
|
24
|
+
</span>
|
|
25
|
+
</div>
|
|
26
|
+
<template lwc:if={withToggle}>
|
|
27
|
+
<c-quantic-generated-answer-toggle
|
|
28
|
+
is-generated-answer-visible={isVisible}
|
|
29
|
+
></c-quantic-generated-answer-toggle>
|
|
30
|
+
</template>
|
|
31
|
+
</header>
|
|
32
|
+
<template lwc:if={isVisible}>
|
|
33
|
+
<div
|
|
34
|
+
class="slds-p-horizontal_large slds-p-top_medium generated-answer__content--scrollable"
|
|
35
|
+
>
|
|
36
|
+
<c-quantic-generated-answer-thread
|
|
37
|
+
engine-id={engineId}
|
|
38
|
+
onquantic__generatedanswerlike={handleLike}
|
|
39
|
+
onquantic__generatedanswerdislike={handleDislike}
|
|
40
|
+
onquantic__citationhover={handleCitationHover}
|
|
41
|
+
generated-answers={allGeneratedAnswers}
|
|
42
|
+
disable-citation-anchoring={disableCitationAnchoring}
|
|
43
|
+
>
|
|
44
|
+
</c-quantic-generated-answer-thread>
|
|
45
|
+
</div>
|
|
46
|
+
<div class="slds-p-horizontal_large slds-p-vertical_medium">
|
|
47
|
+
<c-quantic-generated-answer-follow-up-input
|
|
48
|
+
onquantic__submitfollowup={handleFollowUpSubmit}
|
|
49
|
+
submit-button-disabled={isAnswerGenerationOngoing}
|
|
50
|
+
></c-quantic-generated-answer-follow-up-input>
|
|
51
|
+
<footer
|
|
52
|
+
data-testid="generated-answer__footer"
|
|
53
|
+
class="generated-answer__disclaimer slds-grid slds-grid_vertical-align-center slds-grid_align-end slds-text-color_weak slds-text-body_small slds-var-m-top_xx-small"
|
|
54
|
+
>
|
|
55
|
+
<div data-testid="generated-answer__disclaimer">
|
|
56
|
+
<slot name="disclaimer">{labels.rgaDisclaimer}</slot>
|
|
57
|
+
</div>
|
|
58
|
+
</footer>
|
|
59
|
+
</div>
|
|
60
|
+
</template>
|
|
61
|
+
</div>
|
|
62
|
+
</template>
|
|
63
|
+
</template>
|
|
@@ -9,11 +9,6 @@
|
|
|
9
9
|
word-wrap: break-word;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
.generated-answer__content--scrollable {
|
|
13
|
-
overflow-y: auto;
|
|
14
|
-
height: var(--quantic-generated-answer-content-fixed-height, 50vh);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
12
|
.generated-answer__footer {
|
|
18
13
|
gap: 1rem;
|
|
19
14
|
}
|
|
@@ -59,9 +54,3 @@
|
|
|
59
54
|
opacity: 0.2;
|
|
60
55
|
visibility: hidden;
|
|
61
56
|
}
|
|
62
|
-
|
|
63
|
-
@keyframes cursor-blink {
|
|
64
|
-
100% {
|
|
65
|
-
visibility: hidden;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<template lwc:if={isVisible}>
|
|
28
28
|
<section
|
|
29
29
|
data-testid="generated-answer__body"
|
|
30
|
-
class=
|
|
30
|
+
class="generated-answer__content slds-p-top_medium slds-p-horizontal_large"
|
|
31
31
|
>
|
|
32
32
|
<div
|
|
33
33
|
data-testid="generated-answer__answer"
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
engine-id={engineId}
|
|
51
51
|
data-testid="generated-answer__citations"
|
|
52
52
|
citations={citations}
|
|
53
|
-
citation-hover-handler={
|
|
53
|
+
citation-hover-handler={logCitationHover}
|
|
54
54
|
disable-citation-anchoring={disableCitationAnchoring}
|
|
55
55
|
></c-quantic-source-citations>
|
|
56
56
|
</div>
|
|
@@ -67,6 +67,7 @@ const selectors = {
|
|
|
67
67
|
error: '[data-testid="generated-answer-body__error"]',
|
|
68
68
|
noAnswer: '[data-testid="generated-answer-body__no-answer-message"]',
|
|
69
69
|
content: 'c-quantic-generated-answer-content',
|
|
70
|
+
streamOfThought: 'c-quantic-generated-answer-stream-of-thought',
|
|
70
71
|
};
|
|
71
72
|
|
|
72
73
|
const createTestComponent = buildCreateTestComponent(
|
|
@@ -80,7 +81,7 @@ describe('c-quantic-generated-answer-body', () => {
|
|
|
80
81
|
cleanup();
|
|
81
82
|
});
|
|
82
83
|
|
|
83
|
-
it('should pass answer and
|
|
84
|
+
it('should pass the answer, answerContentFormat, engineId, and answerId properties to the QuanticGeneratedAnswerContent component', async () => {
|
|
84
85
|
const element = createTestComponent();
|
|
85
86
|
await flushPromises();
|
|
86
87
|
|
|
@@ -90,6 +91,42 @@ describe('c-quantic-generated-answer-body', () => {
|
|
|
90
91
|
expect(content.answerContentFormat).toBe(
|
|
91
92
|
defaultOptions.generatedAnswer.answerContentFormat
|
|
92
93
|
);
|
|
94
|
+
expect(content.engineId).toBe(defaultOptions.engineId);
|
|
95
|
+
expect(content.answerId).toBe(defaultOptions.generatedAnswer.answerId);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should pass agentSteps and isStreaming to the QuanticGeneratedAnswerStreamOfThought component', async () => {
|
|
99
|
+
const generationSteps = [{id: 'step-1'}, {id: 'step-2'}];
|
|
100
|
+
const element = createTestComponent({
|
|
101
|
+
...defaultOptions,
|
|
102
|
+
generatedAnswer: {
|
|
103
|
+
...defaultOptions.generatedAnswer,
|
|
104
|
+
// @ts-ignore
|
|
105
|
+
generationSteps,
|
|
106
|
+
isStreaming: true,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
await flushPromises();
|
|
110
|
+
|
|
111
|
+
const streamOfThought = element.shadowRoot.querySelector(
|
|
112
|
+
selectors.streamOfThought
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
expect(streamOfThought).not.toBeNull();
|
|
116
|
+
expect(streamOfThought.agentSteps).toEqual(generationSteps);
|
|
117
|
+
expect(streamOfThought.isStreaming).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should pass an empty agentSteps array to QuanticGeneratedAnswerStreamOfThought when generationSteps is undefined', async () => {
|
|
121
|
+
const element = createTestComponent();
|
|
122
|
+
await flushPromises();
|
|
123
|
+
|
|
124
|
+
const streamOfThought = element.shadowRoot.querySelector(
|
|
125
|
+
selectors.streamOfThought
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
expect(streamOfThought).not.toBeNull();
|
|
129
|
+
expect(streamOfThought.agentSteps).toEqual([]);
|
|
93
130
|
});
|
|
94
131
|
|
|
95
132
|
it('should send the answerId within event details when dispatching the #quantic__generatedanswerlike event', async () => {
|
|
@@ -290,6 +327,7 @@ describe('c-quantic-generated-answer-body', () => {
|
|
|
290
327
|
const citations = element.shadowRoot.querySelector(selectors.citations);
|
|
291
328
|
|
|
292
329
|
expect(citations).not.toBeNull();
|
|
330
|
+
expect(citations.answerId).toBe(defaultOptions.generatedAnswer.answerId);
|
|
293
331
|
});
|
|
294
332
|
|
|
295
333
|
it('should not display citations when citations are empty', async () => {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import couldNotGenerateAnAnswer from '@salesforce/label/c.quantic_CouldNotGenerateAnAnswer';
|
|
2
2
|
import generatedAnswerErrorTurnLimitReached from '@salesforce/label/c.quantic_GeneratedAnswerErrorTurnLimitReached';
|
|
3
3
|
import genericErrorTitle from '@salesforce/label/c.quantic_GenericErrorTitle';
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import like from '@salesforce/label/c.quantic_Like';
|
|
5
|
+
import dislike from '@salesforce/label/c.quantic_Dislike';
|
|
6
6
|
import {LightningElement, api} from 'lwc';
|
|
7
7
|
// @ts-ignore
|
|
8
8
|
import answerTemplate from './templates/answer.html';
|
|
@@ -49,8 +49,8 @@ export default class QuanticGeneratedAnswerBody extends LightningElement {
|
|
|
49
49
|
couldNotGenerateAnAnswer,
|
|
50
50
|
generatedAnswerErrorTurnLimitReached,
|
|
51
51
|
genericErrorTitle,
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
like,
|
|
53
|
+
dislike,
|
|
54
54
|
};
|
|
55
55
|
|
|
56
56
|
get answer() {
|
|
@@ -73,6 +73,10 @@ export default class QuanticGeneratedAnswerBody extends LightningElement {
|
|
|
73
73
|
return !!this.generatedAnswer?.isStreaming;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
get agentSteps() {
|
|
77
|
+
return this.generatedAnswer?.generationSteps || [];
|
|
78
|
+
}
|
|
79
|
+
|
|
76
80
|
get hasError() {
|
|
77
81
|
return !!this.generatedAnswer?.error?.code;
|
|
78
82
|
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<section
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
<section data-testid="generated-answer-body" class="generated-answer__body">
|
|
3
|
+
<div class="slds-var-m-bottom_small">
|
|
4
|
+
<c-quantic-generated-answer-stream-of-thought
|
|
5
|
+
agent-steps={agentSteps}
|
|
6
|
+
is-streaming={isStreaming}
|
|
7
|
+
></c-quantic-generated-answer-stream-of-thought>
|
|
8
|
+
</div>
|
|
6
9
|
<c-quantic-generated-answer-content
|
|
10
|
+
engine-id={engineId}
|
|
11
|
+
answer-id={answerId}
|
|
7
12
|
answer-content-format={answerContentFormat}
|
|
8
13
|
answer={answer}
|
|
9
14
|
is-streaming={isStreaming}
|
|
@@ -18,6 +23,7 @@
|
|
|
18
23
|
citations={citations}
|
|
19
24
|
citation-hover-handler={handleCitationHover}
|
|
20
25
|
disable-citation-anchoring={disableCitationAnchoring}
|
|
26
|
+
answer-id={answerId}
|
|
21
27
|
></c-quantic-source-citations>
|
|
22
28
|
</div>
|
|
23
29
|
</template>
|
|
@@ -26,26 +32,26 @@
|
|
|
26
32
|
data-testid="generated-answer-body__actions"
|
|
27
33
|
class="slds-size_1-of-1 slds-grid slds-grid_vertical-align-center slds-var-m-top_x-small slds-grid_align-start"
|
|
28
34
|
>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
35
|
+
<c-quantic-feedback
|
|
36
|
+
state={computedFeedbackState}
|
|
37
|
+
onquantic__like={handleLike}
|
|
38
|
+
onquantic__dislike={handleDislike}
|
|
39
|
+
like-icon-name="utility:like"
|
|
40
|
+
like-label={labels.like}
|
|
41
|
+
dislike-icon-name="utility:dislike"
|
|
42
|
+
dislike-label={labels.dislike}
|
|
43
|
+
size="x-small"
|
|
44
|
+
question=""
|
|
45
|
+
hide-explain-why-button
|
|
46
|
+
hide-labels
|
|
47
|
+
></c-quantic-feedback>
|
|
48
|
+
<c-quantic-generated-answer-copy-to-clipboard
|
|
49
|
+
data-testid="generated-answer-body__copy-to-clipboard"
|
|
50
|
+
answer={answer}
|
|
51
|
+
answer-id={answerId}
|
|
52
|
+
size="x-small"
|
|
53
|
+
class="slds-var-m-horizontal_xx-small"
|
|
54
|
+
></c-quantic-generated-answer-copy-to-clipboard>
|
|
49
55
|
</div>
|
|
50
56
|
</template>
|
|
51
57
|
</div>
|
|
@@ -34,6 +34,7 @@ const selectors = {
|
|
|
34
34
|
|
|
35
35
|
const defaultOptions = {
|
|
36
36
|
answer: 'Example generated answer',
|
|
37
|
+
answerId: 'example-answer-id',
|
|
37
38
|
size: defaultSize,
|
|
38
39
|
};
|
|
39
40
|
|
|
@@ -109,7 +110,7 @@ describe('c-quantic-generated-answer-copy-to-clipboard', () => {
|
|
|
109
110
|
expect(copyButton.iconSize).toBe(defaultSize);
|
|
110
111
|
});
|
|
111
112
|
|
|
112
|
-
it('should copy the answer and dispatch the #quantic__generatedanswercopy event', async () => {
|
|
113
|
+
it('should copy the answer and dispatch the #quantic__generatedanswercopy event with the answerId', async () => {
|
|
113
114
|
const element = createTestComponent();
|
|
114
115
|
const handler = jest.fn();
|
|
115
116
|
element.addEventListener('quantic__generatedanswercopy', handler);
|
|
@@ -121,5 +122,8 @@ describe('c-quantic-generated-answer-copy-to-clipboard', () => {
|
|
|
121
122
|
|
|
122
123
|
expect(copyToClipboard).toHaveBeenCalledWith(defaultOptions.answer);
|
|
123
124
|
expect(handler).toHaveBeenCalledTimes(1);
|
|
125
|
+
expect(handler.mock.calls[0][0].detail).toEqual({
|
|
126
|
+
answerId: defaultOptions.answerId,
|
|
127
|
+
});
|
|
124
128
|
});
|
|
125
129
|
});
|
|
@@ -15,6 +15,12 @@ export default class QuanticGeneratedAnswerCopyToClipboard extends LightningElem
|
|
|
15
15
|
* @type {string}
|
|
16
16
|
*/
|
|
17
17
|
@api answer = '';
|
|
18
|
+
/**
|
|
19
|
+
* The unique identifier of the generated answer.
|
|
20
|
+
* @api
|
|
21
|
+
* @type {string}
|
|
22
|
+
*/
|
|
23
|
+
@api answerId;
|
|
18
24
|
|
|
19
25
|
/**
|
|
20
26
|
* The size of the copy icon.
|
|
@@ -41,11 +47,12 @@ export default class QuanticGeneratedAnswerCopyToClipboard extends LightningElem
|
|
|
41
47
|
isSelected = false;
|
|
42
48
|
tooltip = this.labels.copy;
|
|
43
49
|
|
|
44
|
-
dispatchCopyToClipboardEvent(
|
|
50
|
+
dispatchCopyToClipboardEvent() {
|
|
45
51
|
this.dispatchEvent(
|
|
46
52
|
new CustomEvent('quantic__generatedanswercopy', {
|
|
47
|
-
detail:
|
|
53
|
+
detail: {answerId: this.answerId},
|
|
48
54
|
bubbles: true,
|
|
55
|
+
composed: true,
|
|
49
56
|
})
|
|
50
57
|
);
|
|
51
58
|
}
|
|
@@ -12,15 +12,18 @@ jest.mock(
|
|
|
12
12
|
{virtual: true}
|
|
13
13
|
);
|
|
14
14
|
jest.mock('c/quanticUtils', () => ({
|
|
15
|
-
keys: {ENTER: 'Enter'},
|
|
15
|
+
keys: {ENTER: 'Enter', ESC: 'Escape'},
|
|
16
16
|
}));
|
|
17
17
|
|
|
18
18
|
const selectors = {
|
|
19
|
-
|
|
19
|
+
textarea: 'textarea',
|
|
20
20
|
submitButton: 'lightning-button-icon',
|
|
21
|
-
|
|
21
|
+
container: '.follow-up-input__container',
|
|
22
|
+
expander: '.follow-up-input__expander',
|
|
22
23
|
};
|
|
23
24
|
|
|
25
|
+
const expandedClass = 'follow-up-input__expander--expanded';
|
|
26
|
+
|
|
24
27
|
const createTestComponent = buildCreateTestComponent(
|
|
25
28
|
QuanticGeneratedAnswerFollowUpInput,
|
|
26
29
|
'c-quantic-generated-answer-follow-up-input'
|
|
@@ -31,17 +34,14 @@ describe('c-quantic-generated-answer-follow-up-input', () => {
|
|
|
31
34
|
cleanup();
|
|
32
35
|
});
|
|
33
36
|
|
|
34
|
-
it('should render the
|
|
37
|
+
it('should render the textarea and submit button', async () => {
|
|
35
38
|
const element = createTestComponent();
|
|
36
39
|
await flushPromises();
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
selectors.submitButton
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
expect(input).not.toBeNull();
|
|
44
|
-
expect(submitButton).not.toBeNull();
|
|
41
|
+
expect(element.shadowRoot.querySelector(selectors.textarea)).not.toBeNull();
|
|
42
|
+
expect(
|
|
43
|
+
element.shadowRoot.querySelector(selectors.submitButton)
|
|
44
|
+
).not.toBeNull();
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
describe('submitting a follow up', () => {
|
|
@@ -52,9 +52,9 @@ describe('c-quantic-generated-answer-follow-up-input', () => {
|
|
|
52
52
|
const handler = jest.fn();
|
|
53
53
|
element.addEventListener('quantic__submitfollowup', handler);
|
|
54
54
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
const textarea = element.shadowRoot.querySelector(selectors.textarea);
|
|
56
|
+
textarea.value = 'follow up question';
|
|
57
|
+
textarea.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter'}));
|
|
58
58
|
|
|
59
59
|
expect(handler).toHaveBeenCalledTimes(1);
|
|
60
60
|
expect(handler.mock.calls[0][0].detail.value).toBe('follow up question');
|
|
@@ -67,29 +67,40 @@ describe('c-quantic-generated-answer-follow-up-input', () => {
|
|
|
67
67
|
const handler = jest.fn();
|
|
68
68
|
element.addEventListener('quantic__submitfollowup', handler);
|
|
69
69
|
|
|
70
|
-
const
|
|
71
|
-
|
|
70
|
+
const textarea = element.shadowRoot.querySelector(selectors.textarea);
|
|
71
|
+
textarea.value = 'another question';
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
selectors.submitButton
|
|
75
|
-
);
|
|
76
|
-
submitButton.click();
|
|
73
|
+
element.shadowRoot.querySelector(selectors.submitButton).click();
|
|
77
74
|
|
|
78
75
|
expect(handler).toHaveBeenCalledTimes(1);
|
|
79
76
|
expect(handler.mock.calls[0][0].detail.value).toBe('another question');
|
|
80
77
|
});
|
|
81
78
|
|
|
82
|
-
it('should clear the
|
|
79
|
+
it('should clear the textarea after a successful submit', async () => {
|
|
83
80
|
const element = createTestComponent();
|
|
84
81
|
await flushPromises();
|
|
85
82
|
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
const textarea = element.shadowRoot.querySelector(selectors.textarea);
|
|
84
|
+
textarea.value = 'a question';
|
|
85
|
+
textarea.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter'}));
|
|
89
86
|
|
|
90
|
-
|
|
87
|
+
expect(textarea.value).toBe('');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should collapse after a successful submit', async () => {
|
|
91
|
+
const element = createTestComponent();
|
|
92
|
+
await flushPromises();
|
|
93
|
+
|
|
94
|
+
const textarea = element.shadowRoot.querySelector(selectors.textarea);
|
|
95
|
+
textarea.dispatchEvent(new CustomEvent('focus'));
|
|
96
|
+
await flushPromises();
|
|
97
|
+
|
|
98
|
+
textarea.value = 'a question';
|
|
99
|
+
textarea.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter'}));
|
|
100
|
+
await flushPromises();
|
|
91
101
|
|
|
92
|
-
|
|
102
|
+
const expander = element.shadowRoot.querySelector(selectors.expander);
|
|
103
|
+
expect(expander.classList).not.toContain(expandedClass);
|
|
93
104
|
});
|
|
94
105
|
});
|
|
95
106
|
|
|
@@ -101,9 +112,9 @@ describe('c-quantic-generated-answer-follow-up-input', () => {
|
|
|
101
112
|
const handler = jest.fn();
|
|
102
113
|
element.addEventListener('quantic__submitfollowup', handler);
|
|
103
114
|
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
115
|
+
const textarea = element.shadowRoot.querySelector(selectors.textarea);
|
|
116
|
+
textarea.value = 'a question';
|
|
117
|
+
textarea.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter'}));
|
|
107
118
|
|
|
108
119
|
expect(handler).not.toHaveBeenCalled();
|
|
109
120
|
});
|
|
@@ -115,9 +126,9 @@ describe('c-quantic-generated-answer-follow-up-input', () => {
|
|
|
115
126
|
const handler = jest.fn();
|
|
116
127
|
element.addEventListener('quantic__submitfollowup', handler);
|
|
117
128
|
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
129
|
+
const textarea = element.shadowRoot.querySelector(selectors.textarea);
|
|
130
|
+
textarea.value = ' ';
|
|
131
|
+
textarea.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter'}));
|
|
121
132
|
|
|
122
133
|
expect(handler).not.toHaveBeenCalled();
|
|
123
134
|
});
|
|
@@ -129,52 +140,95 @@ describe('c-quantic-generated-answer-follow-up-input', () => {
|
|
|
129
140
|
const handler = jest.fn();
|
|
130
141
|
element.addEventListener('quantic__submitfollowup', handler);
|
|
131
142
|
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const submitButton = element.shadowRoot.querySelector(
|
|
136
|
-
selectors.submitButton
|
|
137
|
-
);
|
|
138
|
-
submitButton.click();
|
|
143
|
+
const textarea = element.shadowRoot.querySelector(selectors.textarea);
|
|
144
|
+
textarea.value = '';
|
|
145
|
+
element.shadowRoot.querySelector(selectors.submitButton).click();
|
|
139
146
|
|
|
140
147
|
expect(handler).not.toHaveBeenCalled();
|
|
141
148
|
});
|
|
142
149
|
});
|
|
143
150
|
|
|
144
|
-
describe('
|
|
145
|
-
it('should add
|
|
151
|
+
describe('expandable input behaviour', () => {
|
|
152
|
+
it('should expand and add focused class on focus', async () => {
|
|
146
153
|
const element = createTestComponent();
|
|
147
154
|
await flushPromises();
|
|
148
155
|
|
|
149
|
-
const
|
|
150
|
-
|
|
156
|
+
const textarea = element.shadowRoot.querySelector(selectors.textarea);
|
|
157
|
+
textarea.dispatchEvent(new CustomEvent('focus'));
|
|
151
158
|
await flushPromises();
|
|
152
159
|
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
);
|
|
160
|
+
const expander = element.shadowRoot.querySelector(selectors.expander);
|
|
161
|
+
const container = element.shadowRoot.querySelector(selectors.container);
|
|
162
|
+
expect(expander.classList).toContain(expandedClass);
|
|
156
163
|
expect(container.classList).toContain(
|
|
157
|
-
'follow-up-
|
|
164
|
+
'follow-up-input__container--focused'
|
|
158
165
|
);
|
|
159
166
|
});
|
|
160
167
|
|
|
161
|
-
it('should remove
|
|
168
|
+
it('should collapse and remove focused class on blur', async () => {
|
|
162
169
|
const element = createTestComponent();
|
|
163
170
|
await flushPromises();
|
|
164
171
|
|
|
165
|
-
const
|
|
166
|
-
|
|
172
|
+
const textarea = element.shadowRoot.querySelector(selectors.textarea);
|
|
173
|
+
textarea.dispatchEvent(new CustomEvent('focus'));
|
|
167
174
|
await flushPromises();
|
|
168
175
|
|
|
169
|
-
|
|
176
|
+
textarea.dispatchEvent(new CustomEvent('blur'));
|
|
170
177
|
await flushPromises();
|
|
171
178
|
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
);
|
|
179
|
+
const expander = element.shadowRoot.querySelector(selectors.expander);
|
|
180
|
+
const container = element.shadowRoot.querySelector(selectors.container);
|
|
181
|
+
expect(expander.classList).not.toContain(expandedClass);
|
|
175
182
|
expect(container.classList).not.toContain(
|
|
176
|
-
'follow-up-
|
|
183
|
+
'follow-up-input__container--focused'
|
|
177
184
|
);
|
|
178
185
|
});
|
|
186
|
+
|
|
187
|
+
it('should sync replica text on input', async () => {
|
|
188
|
+
const element = createTestComponent();
|
|
189
|
+
await flushPromises();
|
|
190
|
+
|
|
191
|
+
const textarea = element.shadowRoot.querySelector(selectors.textarea);
|
|
192
|
+
const expander = element.shadowRoot.querySelector(selectors.expander);
|
|
193
|
+
|
|
194
|
+
textarea.value = 'multi\nline\ntext';
|
|
195
|
+
textarea.dispatchEvent(new CustomEvent('input'));
|
|
196
|
+
await flushPromises();
|
|
197
|
+
|
|
198
|
+
expect(expander.dataset.replicatedValue).toBe('multi\nline\ntext');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should clear replica text after submit', async () => {
|
|
202
|
+
const element = createTestComponent();
|
|
203
|
+
await flushPromises();
|
|
204
|
+
|
|
205
|
+
const textarea = element.shadowRoot.querySelector(selectors.textarea);
|
|
206
|
+
const expander = element.shadowRoot.querySelector(selectors.expander);
|
|
207
|
+
|
|
208
|
+
textarea.value = 'some text';
|
|
209
|
+
textarea.dispatchEvent(new CustomEvent('input'));
|
|
210
|
+
await flushPromises();
|
|
211
|
+
expect(expander.dataset.replicatedValue).toBe('some text');
|
|
212
|
+
|
|
213
|
+
textarea.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter'}));
|
|
214
|
+
await flushPromises();
|
|
215
|
+
|
|
216
|
+
expect(expander.dataset.replicatedValue).toBeUndefined();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should collapse the textarea when the Escape key is pressed', async () => {
|
|
220
|
+
const element = createTestComponent();
|
|
221
|
+
await flushPromises();
|
|
222
|
+
|
|
223
|
+
const textarea = element.shadowRoot.querySelector(selectors.textarea);
|
|
224
|
+
textarea.blur = jest.fn();
|
|
225
|
+
textarea.dispatchEvent(new CustomEvent('focus'));
|
|
226
|
+
await flushPromises();
|
|
227
|
+
|
|
228
|
+
textarea.dispatchEvent(new KeyboardEvent('keydown', {key: 'Escape'}));
|
|
229
|
+
await flushPromises();
|
|
230
|
+
|
|
231
|
+
expect(textarea.blur).toHaveBeenCalled();
|
|
232
|
+
});
|
|
179
233
|
});
|
|
180
234
|
});
|
|
@@ -1,17 +1,65 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
.follow-up-input__container {
|
|
2
|
+
min-height: calc(2.5rem + 2px);
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
-
.follow-up-
|
|
5
|
+
.follow-up-input__container--focused {
|
|
6
|
+
border-color: var(--lwc-brandAccessible, #0176d3);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.follow-up-input__expander {
|
|
10
|
+
display: grid;
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
margin-right: 2.5rem;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.follow-up-input__expander::after {
|
|
16
|
+
content: attr(data-replicated-value) ' ';
|
|
17
|
+
visibility: hidden;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.follow-up-input__expander::after,
|
|
21
|
+
.follow-up-input__textarea {
|
|
22
|
+
grid-area: 1 / 1 / 2 / 2;
|
|
23
|
+
resize: none;
|
|
24
|
+
overflow: hidden;
|
|
25
|
+
white-space: nowrap;
|
|
6
26
|
border: none;
|
|
7
|
-
box-shadow: none;
|
|
8
27
|
outline: none;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
28
|
+
background: transparent;
|
|
29
|
+
padding: 0.5rem 0.75rem;
|
|
30
|
+
font: inherit;
|
|
31
|
+
min-height: 2.5rem;
|
|
13
32
|
}
|
|
14
33
|
|
|
15
|
-
.follow-up-
|
|
16
|
-
|
|
17
|
-
|
|
34
|
+
.follow-up-input__expander--expanded {
|
|
35
|
+
position: absolute;
|
|
36
|
+
bottom: -1px;
|
|
37
|
+
left: -1px;
|
|
38
|
+
right: -1px;
|
|
39
|
+
z-index: 10;
|
|
40
|
+
overflow: visible;
|
|
41
|
+
margin-right: 0;
|
|
42
|
+
background-color: var(--lwc-colorBackground, #fff);
|
|
43
|
+
border: 1px solid var(--lwc-brandAccessible, #0176d3);
|
|
44
|
+
border-radius: 0.25rem;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.follow-up-input__expander--expanded::after,
|
|
48
|
+
.follow-up-input__expander--expanded .follow-up-input__textarea {
|
|
49
|
+
white-space: pre-wrap;
|
|
50
|
+
overflow-y: auto;
|
|
51
|
+
max-height: 8rem;
|
|
52
|
+
padding-right: 2.5rem;
|
|
53
|
+
scrollbar-width: none;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.follow-up-input__expander--expanded .follow-up-input__textarea::-webkit-scrollbar {
|
|
57
|
+
display: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.follow-up-input__submit-button {
|
|
61
|
+
position: absolute;
|
|
62
|
+
right: 0.25rem;
|
|
63
|
+
bottom: 0.25rem;
|
|
64
|
+
z-index: 15;
|
|
65
|
+
}
|