@coveo/quantic 3.9.1-pre.bbf2cab18e → 3.10.0-pre.c5a50cf39c

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.
@@ -86,6 +86,9 @@
86
86
  "c/quanticGeneratedAnswerContent": [
87
87
  "quanticGeneratedAnswerContent/quanticGeneratedAnswerContent.js"
88
88
  ],
89
+ "c/quanticGeneratedAnswer": [
90
+ "quanticGeneratedAnswer/quanticGeneratedAnswer.js"
91
+ ],
89
92
  "c/*": ["*"]
90
93
  }
91
94
  },
@@ -0,0 +1,115 @@
1
+ /* eslint-disable no-import-assign */
2
+ // @ts-ignore
3
+ import {createElement} from 'lwc';
4
+ import QuanticGeneratedAnswer from 'c/quanticGeneratedAnswer';
5
+ import * as mockHeadlessLoader from 'c/quanticHeadlessLoader';
6
+
7
+ jest.mock('c/quanticHeadlessLoader');
8
+
9
+ function createTestComponent(options = {}) {
10
+ prepareHeadlessState();
11
+
12
+ const element = createElement('c-quantic-generated-answer', {
13
+ is: QuanticGeneratedAnswer,
14
+ });
15
+ for (const [key, value] of Object.entries(options)) {
16
+ element[key] = value;
17
+ }
18
+
19
+ document.body.appendChild(element);
20
+ return element;
21
+ }
22
+
23
+ const functionsMocks = {
24
+ buildGeneratedAnswer: jest.fn(() => ({
25
+ subscribe: jest.fn((callback) => callback()),
26
+ state: {},
27
+ })),
28
+ buildSearchStatus: jest.fn(() => ({
29
+ subscribe: jest.fn((callback) => callback()),
30
+ state: {},
31
+ })),
32
+ };
33
+
34
+ function prepareHeadlessState() {
35
+ // @ts-ignore
36
+ mockHeadlessLoader.getHeadlessBundle = () => {
37
+ return {
38
+ buildGeneratedAnswer: functionsMocks.buildGeneratedAnswer,
39
+ buildSearchStatus: functionsMocks.buildSearchStatus,
40
+ };
41
+ };
42
+ }
43
+
44
+ // Helper function to wait until the microtask queue is empty.
45
+ function flushPromises() {
46
+ // eslint-disable-next-line @lwc/lwc/no-async-operation
47
+ return new Promise((resolve) => setTimeout(resolve, 0));
48
+ }
49
+
50
+ const exampleEngine = {
51
+ id: 'dummy engine',
52
+ };
53
+ let isInitialized = false;
54
+
55
+ function mockSuccessfulHeadlessInitialization() {
56
+ // @ts-ignore
57
+ mockHeadlessLoader.initializeWithHeadless = (element, _, initialize) => {
58
+ if (element instanceof QuanticGeneratedAnswer && !isInitialized) {
59
+ isInitialized = true;
60
+ initialize(exampleEngine);
61
+ }
62
+ };
63
+ }
64
+
65
+ function cleanup() {
66
+ // The jsdom instance is shared across test cases in a single file so reset the DOM
67
+ while (document.body.firstChild) {
68
+ document.body.removeChild(document.body.firstChild);
69
+ }
70
+ jest.clearAllMocks();
71
+ isInitialized = false;
72
+ }
73
+
74
+ describe('c-quantic-generated-answer', () => {
75
+ beforeAll(() => {
76
+ mockSuccessfulHeadlessInitialization();
77
+ });
78
+
79
+ afterEach(() => {
80
+ cleanup();
81
+ });
82
+
83
+ describe('controller initialization', () => {
84
+ describe('when the answer configuration id property is passed to the component', () => {
85
+ it('should initialize the controller with the correct answer configuration id value', async () => {
86
+ const exampleAnswerConfigValue = 'exampleAnswerConfig';
87
+ createTestComponent({answerConfigurationId: exampleAnswerConfigValue});
88
+ await flushPromises();
89
+
90
+ expect(functionsMocks.buildGeneratedAnswer).toHaveBeenCalledTimes(1);
91
+ expect(functionsMocks.buildGeneratedAnswer).toHaveBeenCalledWith(
92
+ exampleEngine,
93
+ expect.objectContaining({
94
+ answerConfigurationId: exampleAnswerConfigValue,
95
+ })
96
+ );
97
+ });
98
+ });
99
+
100
+ describe('when the answer configuration id property is not passed to the component', () => {
101
+ it('should initialize the controller without the answer configuration id value', async () => {
102
+ createTestComponent();
103
+ await flushPromises();
104
+
105
+ expect(functionsMocks.buildGeneratedAnswer).toHaveBeenCalledTimes(1);
106
+ expect(functionsMocks.buildGeneratedAnswer).toHaveBeenCalledWith(
107
+ exampleEngine,
108
+ expect.not.objectContaining({
109
+ answerConfigurationId: expect.any(String),
110
+ })
111
+ );
112
+ });
113
+ });
114
+ });
115
+ });
@@ -80,6 +80,12 @@ export default class QuanticGeneratedAnswer extends LightningElement {
80
80
  * @default {false}
81
81
  */
82
82
  @api withToggle = false;
83
+ /**
84
+ * @internal
85
+ * The unique identifier of the answer configuration to use to generate the answer.
86
+ * @type {string}
87
+ */
88
+ @api answerConfigurationId;
83
89
 
84
90
  labels = {
85
91
  generatedAnswerForYou,
@@ -187,6 +193,9 @@ export default class QuanticGeneratedAnswer extends LightningElement {
187
193
  contentFormat: ['text/markdown', 'text/plain'],
188
194
  },
189
195
  },
196
+ ...(this.answerConfigurationId && {
197
+ answerConfigurationId: this.answerConfigurationId,
198
+ }),
190
199
  fieldsToIncludeInCitations: this.citationFields,
191
200
  });
192
201
  }
@@ -421,12 +430,7 @@ export default class QuanticGeneratedAnswer extends LightningElement {
421
430
 
422
431
  get shouldDisplayGeneratedAnswer() {
423
432
  const hasCitations = !!this.citations?.length;
424
- return (
425
- !!this.answer ||
426
- this.isStreaming ||
427
- hasCitations ||
428
- this.hasRetryableError
429
- );
433
+ return !!this.answer || hasCitations || this.hasRetryableError;
430
434
  }
431
435
 
432
436
  get generatedAnswerClass() {
@@ -1,6 +1,17 @@
1
1
  // @ts-ignore
2
2
  import QuanticGeneratedAnswerContent from 'c/quanticGeneratedAnswerContent';
3
3
  import {createElement} from 'lwc';
4
+ import {loadMarkdownDependencies} from 'c/quanticUtils';
5
+
6
+ jest.mock('c/quanticUtils', () => ({
7
+ loadMarkdownDependencies: jest.fn(
8
+ () =>
9
+ new Promise((resolve) => {
10
+ resolve();
11
+ })
12
+ ),
13
+ transformMarkdownToHtml: jest.fn((value) => value),
14
+ }));
4
15
 
5
16
  const mockMarkedUse = jest.fn();
6
17
  const mockMarkedParse = jest.fn((text) => text);
@@ -122,4 +133,19 @@ describe('c-quantic-generated-answer-content', () => {
122
133
  );
123
134
  });
124
135
  });
136
+
137
+ describe('the reactivity of the answerContentFormat property', () => {
138
+ it('should load the markdown dependencies when the answerContentFormat property is changed to text/markdown', async () => {
139
+ const element = createTestComponent({
140
+ ...defaultOptions,
141
+ answerContentFormat: 'text/plain',
142
+ });
143
+ await flushPromises();
144
+ expect(loadMarkdownDependencies).not.toHaveBeenCalled();
145
+
146
+ element.answerContentFormat = 'text/markdown';
147
+ await flushPromises();
148
+ expect(loadMarkdownDependencies).toHaveBeenCalled();
149
+ });
150
+ });
125
151
  });
@@ -28,7 +28,19 @@ export default class QuanticGeneratedAnswerContent extends LightningElement {
28
28
  * @type {'text/plain' | 'text/markdown'}
29
29
  * @default {'text/plain'}
30
30
  */
31
- @api answerContentFormat = 'text/plain';
31
+ @api
32
+ get answerContentFormat() {
33
+ return this._answerContentFormat;
34
+ }
35
+ set answerContentFormat(value) {
36
+ this._answerContentFormat = value;
37
+ if (
38
+ this._answerContentFormat === 'text/markdown' &&
39
+ !this._markdownDependenciesLoaded
40
+ ) {
41
+ this.loadMarkdownDependencies();
42
+ }
43
+ }
32
44
  /**
33
45
  * The answer content to display.
34
46
  * @api
@@ -42,30 +54,29 @@ export default class QuanticGeneratedAnswerContent extends LightningElement {
42
54
  this._answer = value;
43
55
  if (
44
56
  this.answerContentFormat === 'text/markdown' &&
45
- this._markedLoaded &&
46
- this._domPurifyLoaded
57
+ this._markdownDependenciesLoaded
47
58
  ) {
48
59
  this.updateHtmlContent();
49
60
  }
50
61
  }
51
62
 
52
63
  _answer;
53
- _markedLoaded = false;
54
- _domPurifyLoaded = false;
64
+ /** @type {'text/plain' | 'text/markdown'} */
65
+ _answerContentFormat = 'text/plain';
66
+ _markdownDependenciesLoaded = false;
55
67
 
56
- connectedCallback() {
57
- if (this.answerContentFormat === 'text/markdown') {
68
+ loadMarkdownDependencies() {
69
+ if (
70
+ !this._markdownDependenciesLoaded &&
71
+ this.answerContentFormat === 'text/markdown'
72
+ ) {
58
73
  loadMarkdownDependencies(this)
59
74
  .then(() => {
60
- // @ts-ignore
61
- this._markedLoaded = true;
62
- // @ts-ignore
63
- this._domPurifyLoaded = true;
75
+ this._markdownDependenciesLoaded = true;
64
76
  this.updateHtmlContent();
65
77
  })
66
78
  .catch((error) => {
67
79
  console.error('Error loading the Marked library.', error);
68
- this._markedLoadingError = true;
69
80
  });
70
81
  }
71
82
  }
@@ -74,11 +85,12 @@ export default class QuanticGeneratedAnswerContent extends LightningElement {
74
85
  const answerContainer = this.template.querySelector(
75
86
  '.generated-answer-content__answer'
76
87
  );
77
- if (this._markedLoaded && this._domPurifyLoaded) {
88
+ if (this._markdownDependenciesLoaded) {
78
89
  // Transform the markdown answer to HTML and update the innerHTML of the container
79
90
  const newHTMLContent =
80
91
  // @ts-ignore
81
92
  (window.marked &&
93
+ this.answer &&
82
94
  // @ts-ignore
83
95
  transformMarkdownToHtml(this.answer, window.marked)) ||
84
96
  '';