@eeacms/volto-eea-chatbot 2.0.0 → 2.0.2

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 (81) hide show
  1. package/.eslintrc.js +6 -6
  2. package/CHANGELOG.md +12 -1
  3. package/jest-addon.config.js +1 -0
  4. package/package.json +1 -1
  5. package/src/ChatBlock/ChatBlockEdit.jsx +2 -1
  6. package/src/ChatBlock/chat/AIMessage.tsx +20 -16
  7. package/src/ChatBlock/chat/ChatMessage.tsx +1 -1
  8. package/src/ChatBlock/chat/ChatWindow.tsx +10 -11
  9. package/src/ChatBlock/chat/UserMessage.tsx +4 -4
  10. package/src/ChatBlock/components/AutoResizeTextarea.jsx +1 -1
  11. package/src/ChatBlock/components/ChatMessageFeedback.jsx +2 -2
  12. package/src/ChatBlock/components/EmptyState.jsx +1 -1
  13. package/src/ChatBlock/components/FeedbackModal.jsx +1 -1
  14. package/src/ChatBlock/components/HalloumiFeedback.jsx +2 -2
  15. package/src/ChatBlock/components/Source.jsx +2 -2
  16. package/src/ChatBlock/components/UserActionsToolbar.jsx +3 -3
  17. package/src/ChatBlock/components/WebResultIcon.tsx +2 -2
  18. package/src/ChatBlock/components/markdown/ClaimModal.jsx +3 -3
  19. package/src/ChatBlock/components/markdown/ClaimSegments.jsx +4 -4
  20. package/src/ChatBlock/components/markdown/{index.js → index.jsx} +1 -1
  21. package/src/ChatBlock/hooks/useChatController.ts +7 -4
  22. package/src/ChatBlock/hooks/useChatStreaming.ts +4 -4
  23. package/src/ChatBlock/hooks/useToolDisplayTiming.ts +1 -1
  24. package/src/ChatBlock/index.js +8 -0
  25. package/src/ChatBlock/packets/MultiToolRenderer.tsx +11 -12
  26. package/src/ChatBlock/packets/RendererComponent.tsx +6 -3
  27. package/src/ChatBlock/packets/renderers/CustomToolRenderer.tsx +3 -3
  28. package/src/ChatBlock/packets/renderers/FetchToolRenderer.tsx +3 -3
  29. package/src/ChatBlock/packets/renderers/ImageToolRenderer.tsx +3 -3
  30. package/src/ChatBlock/packets/renderers/MessageTextRenderer.tsx +8 -8
  31. package/src/ChatBlock/packets/renderers/ReasoningRenderer.tsx +5 -5
  32. package/src/ChatBlock/packets/renderers/SearchToolRenderer.tsx +10 -10
  33. package/src/ChatBlock/services/messageProcessor.ts +6 -3
  34. package/src/ChatBlock/services/packetUtils.ts +2 -2
  35. package/src/ChatBlock/services/streamingService.ts +8 -2
  36. package/src/ChatBlock/utils/citations.ts +1 -1
  37. package/src/halloumi/filtering.test.js +199 -1
  38. package/src/ChatBlock/tests/AIMessage.test.jsx +0 -95
  39. package/src/ChatBlock/tests/AutoResizeTextarea.test.jsx +0 -49
  40. package/src/ChatBlock/tests/BlinkingDot.test.jsx +0 -71
  41. package/src/ChatBlock/tests/ChatMessage.test.jsx +0 -75
  42. package/src/ChatBlock/tests/ChatMessageFeedback.test.jsx +0 -73
  43. package/src/ChatBlock/tests/Citation.test.jsx +0 -107
  44. package/src/ChatBlock/tests/ClaimModal.test.jsx +0 -136
  45. package/src/ChatBlock/tests/ClaimSegments.test.jsx +0 -206
  46. package/src/ChatBlock/tests/CustomToolRenderer.test.jsx +0 -241
  47. package/src/ChatBlock/tests/EmptyState.test.jsx +0 -137
  48. package/src/ChatBlock/tests/FeedbackModal.test.jsx +0 -138
  49. package/src/ChatBlock/tests/FetchToolRenderer.test.jsx +0 -161
  50. package/src/ChatBlock/tests/HalloumiFeedback.test.jsx +0 -94
  51. package/src/ChatBlock/tests/ImageToolRenderer.test.jsx +0 -178
  52. package/src/ChatBlock/tests/MessageTextRenderer.test.jsx +0 -227
  53. package/src/ChatBlock/tests/MultiToolRenderer.test.jsx +0 -134
  54. package/src/ChatBlock/tests/QualityCheckToggle.test.jsx +0 -105
  55. package/src/ChatBlock/tests/ReasoningRenderer.test.jsx +0 -163
  56. package/src/ChatBlock/tests/RelatedQuestions.test.jsx +0 -215
  57. package/src/ChatBlock/tests/RenderClaimView.test.jsx +0 -191
  58. package/src/ChatBlock/tests/RendererComponent.test.jsx +0 -139
  59. package/src/ChatBlock/tests/SearchToolRenderer.test.jsx +0 -295
  60. package/src/ChatBlock/tests/Source.test.jsx +0 -79
  61. package/src/ChatBlock/tests/SourceChip.test.jsx +0 -108
  62. package/src/ChatBlock/tests/Spinner.test.jsx +0 -18
  63. package/src/ChatBlock/tests/UserActionsToolbar.test.jsx +0 -135
  64. package/src/ChatBlock/tests/UserMessage.test.jsx +0 -83
  65. package/src/ChatBlock/tests/WebResultIcon.test.jsx +0 -61
  66. package/src/ChatBlock/tests/citations.test.js +0 -114
  67. package/src/ChatBlock/tests/index.test.js +0 -51
  68. package/src/ChatBlock/tests/messageProcessor.test.jsx +0 -438
  69. package/src/ChatBlock/tests/packetUtils.test.js +0 -158
  70. package/src/ChatBlock/tests/schema.test.js +0 -166
  71. package/src/ChatBlock/tests/streamingService.test.js +0 -467
  72. package/src/ChatBlock/tests/useChatController.test.jsx +0 -268
  73. package/src/ChatBlock/tests/useChatStreaming.test.jsx +0 -163
  74. package/src/ChatBlock/tests/useDeepCompareMemoize.test.js +0 -107
  75. package/src/ChatBlock/tests/useMarked.test.jsx +0 -107
  76. package/src/ChatBlock/tests/useQualityMarkers.test.jsx +0 -150
  77. package/src/ChatBlock/tests/useScrollonStream.test.jsx +0 -121
  78. package/src/ChatBlock/tests/useToolDisplayTiming.test.jsx +0 -151
  79. package/src/ChatBlock/tests/utils.test.jsx +0 -241
  80. package/src/ChatBlock/tests/withOnyxData.test.jsx +0 -81
  81. /package/src/ChatBlock/{schema.js → schema.jsx} +0 -0
@@ -1,5 +1,11 @@
1
- import { PacketType, type Packet } from '../types/streamingModels';
2
- import type { FileDescriptor, Filters } from '../types/interfaces';
1
+ import {
2
+ PacketType,
3
+ type Packet,
4
+ } from '@eeacms/volto-eea-chatbot/ChatBlock/types/streamingModels';
5
+ import type {
6
+ FileDescriptor,
7
+ Filters,
8
+ } from '@eeacms/volto-eea-chatbot/ChatBlock/types/interfaces';
3
9
 
4
10
  export interface SendMessageParams {
5
11
  regenerate: boolean;
@@ -1,4 +1,4 @@
1
- import type { Message } from '../types/interfaces';
1
+ import type { Message } from '@eeacms/volto-eea-chatbot/ChatBlock/types/interfaces';
2
2
 
3
3
  /**
4
4
  * Regex to match citation markers like [1], [2], etc.
@@ -1,4 +1,12 @@
1
- import { parseExcludeIndices } from './filtering';
1
+ import fetch from 'node-fetch';
2
+
3
+ import {
4
+ parseExcludeIndices,
5
+ callLLM,
6
+ excludeClaimSentences,
7
+ excludeContextSentences,
8
+ } from './filtering';
9
+ jest.mock('node-fetch');
2
10
 
3
11
  describe('parseExcludeIndices', () => {
4
12
  it('parses single indices', () => {
@@ -42,3 +50,193 @@ describe('parseExcludeIndices', () => {
42
50
  expect(result).toEqual(new Set());
43
51
  });
44
52
  });
53
+
54
+ describe('callLLM', () => {
55
+ beforeEach(() => {
56
+ fetch.mockReset();
57
+ });
58
+
59
+ afterEach(() => {
60
+ fetch.mockReset();
61
+ });
62
+
63
+ it('makes POST request with correct headers', async () => {
64
+ const mockResponse = { choices: [{ message: { content: 'result' } }] };
65
+ fetch.mockResolvedValue({
66
+ json: () => Promise.resolve(mockResponse),
67
+ });
68
+
69
+ await callLLM('https://api.example.com', 'my-api-key', { model: 'gpt-4' });
70
+
71
+ expect(fetch).toHaveBeenCalledWith(
72
+ 'https://api.example.com',
73
+ expect.objectContaining({
74
+ method: 'POST',
75
+ headers: expect.objectContaining({
76
+ 'Content-Type': 'application/json',
77
+ Authorization: 'Bearer my-api-key',
78
+ }),
79
+ }),
80
+ );
81
+ });
82
+
83
+ it('omits Authorization header when no apiKey', async () => {
84
+ fetch.mockResolvedValue({
85
+ json: () => Promise.resolve({}),
86
+ });
87
+
88
+ await callLLM('https://api.example.com', null, { model: 'gpt-4' });
89
+
90
+ const headers = fetch.mock.calls[0][1].headers;
91
+ expect(headers['Authorization']).toBeUndefined();
92
+ });
93
+
94
+ it('adds X-Forwarded-For header when ip is provided', async () => {
95
+ fetch.mockResolvedValue({
96
+ json: () => Promise.resolve({}),
97
+ });
98
+
99
+ await callLLM('https://api.example.com', null, {}, { ip: '1.2.3.4' });
100
+
101
+ const headers = fetch.mock.calls[0][1].headers;
102
+ expect(headers['X-Forwarded-For']).toBe('1.2.3.4');
103
+ });
104
+
105
+ it('returns parsed JSON response', async () => {
106
+ const mockResponse = { choices: [{ message: { content: 'hello' } }] };
107
+ fetch.mockResolvedValue({
108
+ json: () => Promise.resolve(mockResponse),
109
+ });
110
+
111
+ const result = await callLLM('https://api.example.com', null, {});
112
+ expect(result).toEqual(mockResponse);
113
+ });
114
+ });
115
+
116
+ describe('excludeClaimSentences', () => {
117
+ beforeEach(() => {
118
+ fetch.mockReset();
119
+ });
120
+
121
+ afterEach(() => {
122
+ fetch.mockReset();
123
+ });
124
+
125
+ it('returns empty set for empty sentences array', async () => {
126
+ const result = await excludeClaimSentences([]);
127
+ expect(result).toEqual(new Set());
128
+ expect(fetch).not.toHaveBeenCalled();
129
+ });
130
+
131
+ it('calls LLM and returns excluded sentence indices', async () => {
132
+ fetch.mockResolvedValue({
133
+ json: () =>
134
+ Promise.resolve({
135
+ choices: [{ message: { content: '1,3' } }],
136
+ }),
137
+ });
138
+
139
+ const sentences = ['Sentence one.', 'Sentence two.', 'Sentence three.'];
140
+ const result = await excludeClaimSentences(sentences);
141
+
142
+ expect(result).toEqual(new Set([1, 3]));
143
+ expect(fetch).toHaveBeenCalledTimes(1);
144
+ });
145
+
146
+ it('returns empty set when LLM returns NONE', async () => {
147
+ fetch.mockResolvedValue({
148
+ json: () =>
149
+ Promise.resolve({
150
+ choices: [{ message: { content: 'NONE' } }],
151
+ }),
152
+ });
153
+
154
+ const sentences = ['Some factual sentence.', 'Another factual sentence.'];
155
+ const result = await excludeClaimSentences(sentences);
156
+
157
+ expect(result).toEqual(new Set());
158
+ });
159
+
160
+ it('returns empty set on LLM error', async () => {
161
+ fetch.mockRejectedValue(new Error('Network error'));
162
+
163
+ const sentences = ['Sentence one.'];
164
+ const result = await excludeClaimSentences(sentences);
165
+
166
+ expect(result).toEqual(new Set());
167
+ });
168
+
169
+ it('passes ip option to LLM call', async () => {
170
+ fetch.mockResolvedValue({
171
+ json: () =>
172
+ Promise.resolve({ choices: [{ message: { content: 'NONE' } }] }),
173
+ });
174
+
175
+ await excludeClaimSentences(['test sentence'], { ip: '10.0.0.1' });
176
+
177
+ const headers = fetch.mock.calls[0][1].headers;
178
+ expect(headers['X-Forwarded-For']).toBe('10.0.0.1');
179
+ });
180
+ });
181
+
182
+ describe('excludeContextSentences', () => {
183
+ const MIN_SENTENCES = 75;
184
+
185
+ beforeEach(() => {
186
+ fetch.mockReset();
187
+ });
188
+
189
+ afterEach(() => {
190
+ fetch.mockReset();
191
+ });
192
+
193
+ it('returns empty set when context sentences <= minimum threshold', async () => {
194
+ const shortContext = Array(10).fill('Short sentence.');
195
+ const claims = ['A claim.'];
196
+ const result = await excludeContextSentences(shortContext, claims);
197
+
198
+ expect(result).toEqual(new Set());
199
+ expect(fetch).not.toHaveBeenCalled();
200
+ });
201
+
202
+ it('calls LLM when context exceeds minimum threshold', async () => {
203
+ fetch.mockResolvedValue({
204
+ json: () =>
205
+ Promise.resolve({
206
+ choices: [{ message: { content: '1,2' } }],
207
+ }),
208
+ });
209
+
210
+ const longContext = Array(MIN_SENTENCES + 1).fill('Context sentence.');
211
+ const claims = ['A claim.'];
212
+ const result = await excludeContextSentences(longContext, claims);
213
+
214
+ expect(result).toEqual(new Set([1, 2]));
215
+ expect(fetch).toHaveBeenCalledTimes(1);
216
+ });
217
+
218
+ it('returns empty set on LLM error', async () => {
219
+ fetch.mockRejectedValue(new Error('timeout'));
220
+
221
+ const longContext = Array(MIN_SENTENCES + 1).fill('Context sentence.');
222
+ const claims = ['A claim.'];
223
+ const result = await excludeContextSentences(longContext, claims);
224
+
225
+ expect(result).toEqual(new Set());
226
+ });
227
+
228
+ it('returns empty set when LLM returns NONE for context', async () => {
229
+ fetch.mockResolvedValue({
230
+ json: () =>
231
+ Promise.resolve({
232
+ choices: [{ message: { content: 'NONE' } }],
233
+ }),
234
+ });
235
+
236
+ const longContext = Array(MIN_SENTENCES + 1).fill('Context sentence.');
237
+ const claims = ['A claim.'];
238
+ const result = await excludeContextSentences(longContext, claims);
239
+
240
+ expect(result).toEqual(new Set());
241
+ });
242
+ });
@@ -1,95 +0,0 @@
1
- import { MemoryRouter } from 'react-router-dom';
2
- import configureStore from 'redux-mock-store';
3
- import renderer from 'react-test-renderer';
4
-
5
- import '@testing-library/jest-dom';
6
- import { Provider } from 'react-intl-redux';
7
- import { AIMessage } from '../chat/AIMessage';
8
-
9
- const mockStore = configureStore();
10
-
11
- describe('AIMessage', () => {
12
- let store;
13
-
14
- beforeEach(() => {
15
- store = mockStore({
16
- userSession: { token: '1234' },
17
- intl: { locale: 'en', messages: {} },
18
- });
19
- });
20
-
21
- const renderComponent = (props) =>
22
- renderer.create(
23
- <Provider store={store}>
24
- <MemoryRouter>
25
- <AIMessage {...props} />
26
- </MemoryRouter>
27
- </Provider>,
28
- );
29
-
30
- it('renders AI message with content', () => {
31
- const props = {
32
- message: {
33
- messageId: 1,
34
- message: 'Hello, I am an AI assistant',
35
- type: 'assistant',
36
- },
37
- };
38
-
39
- const component = renderComponent(props);
40
- const json = component.toJSON();
41
- expect(json).toMatchSnapshot();
42
- });
43
-
44
- it('renders AI message with sources', () => {
45
- const props = {
46
- message: {
47
- messageId: 1,
48
- message: 'Here is some information',
49
- type: 'assistant',
50
- documents: [
51
- {
52
- document_id: 'doc1',
53
- semantic_identifier: 'Source 1',
54
- link: 'https://example.com/1',
55
- source_type: 'web',
56
- },
57
- ],
58
- },
59
- };
60
-
61
- const component = renderComponent(props);
62
- const json = component.toJSON();
63
- expect(json).toMatchSnapshot();
64
- });
65
-
66
- it('renders AI message with feedback options', () => {
67
- const props = {
68
- message: {
69
- messageId: 1,
70
- message: 'This is a response',
71
- type: 'assistant',
72
- },
73
- onFeedback: jest.fn(),
74
- enableFeedback: true,
75
- };
76
-
77
- const component = renderComponent(props);
78
- const json = component.toJSON();
79
- expect(json).toMatchSnapshot();
80
- });
81
-
82
- it('renders empty AI message', () => {
83
- const props = {
84
- message: {
85
- messageId: 1,
86
- message: '',
87
- type: 'assistant',
88
- },
89
- };
90
-
91
- const component = renderComponent(props);
92
- const json = component.toJSON();
93
- expect(json).toMatchSnapshot();
94
- });
95
- });
@@ -1,49 +0,0 @@
1
- import { render, fireEvent } from '@testing-library/react';
2
- import '@testing-library/jest-dom';
3
- import AutoResizeTextarea from '../components/AutoResizeTextarea';
4
-
5
- jest.mock('@eeacms/volto-matomo/utils', () => ({
6
- trackEvent: jest.fn(),
7
- }));
8
-
9
- describe('AutoResizeTextarea', () => {
10
- it('renders textarea and button', () => {
11
- const { getByRole, getByLabelText } = render(
12
- <AutoResizeTextarea onSubmit={jest.fn()} />,
13
- );
14
-
15
- expect(getByRole('textbox')).toBeInTheDocument();
16
- expect(getByLabelText('Send')).toBeInTheDocument();
17
- });
18
-
19
- it('calls onSubmit with input text on Enter key press', () => {
20
- const mockSubmit = jest.fn();
21
- const { getByRole } = render(<AutoResizeTextarea onSubmit={mockSubmit} />);
22
- const textarea = getByRole('textbox');
23
-
24
- fireEvent.change(textarea, { target: { value: 'Hello' } });
25
- fireEvent.keyDown(textarea, { key: 'Enter', code: 'Enter' });
26
-
27
- expect(mockSubmit).toHaveBeenCalledWith({ message: 'Hello' });
28
- });
29
-
30
- it('does not call onSubmit if input is empty', () => {
31
- const mockSubmit = jest.fn();
32
- const { getByRole } = render(<AutoResizeTextarea onSubmit={mockSubmit} />);
33
- const textarea = getByRole('textbox');
34
-
35
- fireEvent.keyDown(textarea, { key: 'Enter', code: 'Enter' });
36
-
37
- expect(mockSubmit).not.toHaveBeenCalled();
38
- });
39
-
40
- it('adds newline on Shift+Enter', () => {
41
- const { getByRole } = render(<AutoResizeTextarea onSubmit={jest.fn()} />);
42
- const textarea = getByRole('textbox');
43
-
44
- fireEvent.change(textarea, { target: { value: 'Line 1' } });
45
- fireEvent.keyDown(textarea, { key: 'Enter', shiftKey: true });
46
-
47
- expect(textarea.value).toBe('Line 1\n');
48
- });
49
- });
@@ -1,71 +0,0 @@
1
- import { MemoryRouter } from 'react-router-dom';
2
- import configureStore from 'redux-mock-store';
3
- import renderer from 'react-test-renderer';
4
-
5
- import '@testing-library/jest-dom';
6
- import { Provider } from 'react-intl-redux';
7
- import { BlinkingDot } from '../components/BlinkingDot';
8
-
9
- const mockStore = configureStore();
10
-
11
- describe('BlinkingDot', () => {
12
- let store;
13
-
14
- beforeEach(() => {
15
- store = mockStore({
16
- userSession: { token: '1234' },
17
- intl: { locale: 'en', messages: {} },
18
- });
19
- });
20
-
21
- const renderComponent = (props) =>
22
- renderer.create(
23
- <Provider store={store}>
24
- <MemoryRouter>
25
- <BlinkingDot {...props} />
26
- </MemoryRouter>
27
- </Provider>,
28
- );
29
-
30
- it('renders blinking dot when active', () => {
31
- const props = {
32
- isActive: true,
33
- };
34
-
35
- const component = renderComponent(props);
36
- const json = component.toJSON();
37
- expect(json).toMatchSnapshot();
38
- });
39
-
40
- it('renders inactive dot when not active', () => {
41
- const props = {
42
- isActive: false,
43
- };
44
-
45
- const component = renderComponent(props);
46
- const json = component.toJSON();
47
- expect(json).toMatchSnapshot();
48
- });
49
-
50
- it('applies custom className', () => {
51
- const props = {
52
- isActive: true,
53
- className: 'custom-class',
54
- };
55
-
56
- const component = renderComponent(props);
57
- const json = component.toJSON();
58
- expect(json).toMatchSnapshot();
59
- });
60
-
61
- it('applies custom size', () => {
62
- const props = {
63
- isActive: true,
64
- size: 20,
65
- };
66
-
67
- const component = renderComponent(props);
68
- const json = component.toJSON();
69
- expect(json).toMatchSnapshot();
70
- });
71
- });
@@ -1,75 +0,0 @@
1
- import { MemoryRouter } from 'react-router-dom';
2
- import configureStore from 'redux-mock-store';
3
- import renderer from 'react-test-renderer';
4
-
5
- import '@testing-library/jest-dom';
6
- import { Provider } from 'react-intl-redux';
7
- import { ChatMessage } from '../chat/ChatMessage';
8
-
9
- const mockStore = configureStore();
10
-
11
- // Mock loadable components
12
- jest.mock('@loadable/component', () => {
13
- const loadable = () => {
14
- const MockComponent = ({ children }) => <div>{children}</div>;
15
- return MockComponent;
16
- };
17
- loadable.lib = () => {
18
- const MockComponent = ({ children }) =>
19
- children ? children({ default: {} }) : null;
20
- return MockComponent;
21
- };
22
- return { __esModule: true, default: loadable };
23
- });
24
-
25
- describe('ChatMessage', () => {
26
- let store;
27
-
28
- beforeEach(() => {
29
- store = mockStore({
30
- userSession: { token: '1234' },
31
- intl: { locale: 'en', messages: {} },
32
- });
33
- });
34
-
35
- const renderComponent = (props) =>
36
- renderer.create(
37
- <Provider store={store}>
38
- <MemoryRouter>
39
- <ChatMessage {...props} />
40
- </MemoryRouter>
41
- </Provider>,
42
- );
43
-
44
- it('renders error message correctly', () => {
45
- const props = {
46
- message: {
47
- messageId: 3,
48
- type: 'error',
49
- error: 'Something went wrong',
50
- },
51
- libs: { remarkGfm: { default: [] } },
52
- isLoading: false,
53
- };
54
-
55
- const component = renderComponent(props);
56
- const json = component.toJSON();
57
- expect(json).toMatchSnapshot();
58
- });
59
-
60
- it('returns null for unknown message type', () => {
61
- const props = {
62
- message: {
63
- messageId: 4,
64
- message: 'Unknown type',
65
- type: 'unknown',
66
- },
67
- libs: { remarkGfm: { default: [] } },
68
- isLoading: false,
69
- };
70
-
71
- const component = renderComponent(props);
72
- const json = component.toJSON();
73
- expect(json).toBeNull();
74
- });
75
- });
@@ -1,73 +0,0 @@
1
- import '@testing-library/jest-dom';
2
- import { render, screen, fireEvent } from '@testing-library/react';
3
- import ChatMessageFeedback from '../components/ChatMessageFeedback';
4
-
5
- jest.mock('../components/FeedbackModal', () => (props) => {
6
- const { modalOpen, onClose, onToast } = props;
7
-
8
- return modalOpen ? (
9
- <div data-testid="feedback-modal">
10
- Modal Open
11
- <button
12
- onClick={() => {
13
- onToast('Thank you for your feedback!', 'success');
14
- onClose();
15
- }}
16
- >
17
- Submit Feedback
18
- </button>
19
- </div>
20
- ) : null;
21
- });
22
-
23
- jest.mock('../components/Icon', () => ({ name }) => (
24
- <img src={name} alt="icon" />
25
- ));
26
-
27
- jest.mock('../components/markdown', () => ({
28
- SVGIcon: ({ name }) => <img src={name} alt="icon" />,
29
- }));
30
-
31
- jest.mock('../../icons/thumbs-up.svg', () => 'thumbs-up.svg');
32
- jest.mock('../../icons/thumbs-down.svg', () => 'thumbs-down.svg');
33
-
34
- describe('ChatMessageFeedback', () => {
35
- const defaultProps = {
36
- message: {
37
- messageId: 1,
38
- message: 'Test message',
39
- type: 'assistant',
40
- },
41
- feedbackReasons: ['Reason 1', 'Reason 2'],
42
- };
43
-
44
- it('renders Like and Dislike buttons', () => {
45
- render(<ChatMessageFeedback {...defaultProps} />);
46
- expect(screen.getByLabelText('Like')).toBeInTheDocument();
47
- expect(screen.getByLabelText('Dislike')).toBeInTheDocument();
48
- });
49
-
50
- it('opens modal when Like is clicked', () => {
51
- render(<ChatMessageFeedback {...defaultProps} />);
52
- fireEvent.click(screen.getByLabelText('Like'));
53
- expect(screen.getByTestId('feedback-modal')).toBeInTheDocument();
54
- });
55
-
56
- it('opens modal when Dislike is clicked', () => {
57
- render(<ChatMessageFeedback {...defaultProps} />);
58
- fireEvent.click(screen.getByLabelText('Dislike'));
59
- expect(screen.getByTestId('feedback-modal')).toBeInTheDocument();
60
- });
61
-
62
- it('shows toast after submitting feedback in modal', () => {
63
- render(<ChatMessageFeedback {...defaultProps} />);
64
- fireEvent.click(screen.getByLabelText('Like'));
65
-
66
- const submitButton = screen.getByText('Submit Feedback');
67
- fireEvent.click(submitButton);
68
-
69
- expect(
70
- screen.getByText('Thank you for your feedback!'),
71
- ).toBeInTheDocument();
72
- });
73
- });
@@ -1,107 +0,0 @@
1
- import { MemoryRouter } from 'react-router-dom';
2
- import configureStore from 'redux-mock-store';
3
- import renderer from 'react-test-renderer';
4
-
5
- import '@testing-library/jest-dom';
6
- import { Provider } from 'react-intl-redux';
7
- import { Citation } from '../components/markdown/Citation';
8
-
9
- const mockStore = configureStore();
10
-
11
- describe('Citation', () => {
12
- it('should render the component with link', () => {
13
- const store = mockStore({
14
- userSession: { token: '1234' },
15
- intl: {
16
- locale: 'en',
17
- messages: {},
18
- },
19
- });
20
-
21
- const component = renderer.create(
22
- <Provider store={store}>
23
- <MemoryRouter>
24
- <Citation
25
- message={{
26
- messageId: 6428,
27
- message: 'Donec quam felis ultricies nec',
28
- type: 'assistant',
29
- query: 'Pellentesque libero tortor tincidunt et?',
30
- documents: [
31
- {
32
- document_id: 'https://www.example.com',
33
- semantic_identifier: 'Nam ipsum risus rutrum vitae',
34
- link: 'https://www.example.com',
35
- blurb:
36
- 'Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. ',
37
- source_type: 'web',
38
- match_highlights: ['', 'Praesent ac sem eget est', ''],
39
- updated_at: null,
40
- db_doc_id: 99186,
41
- },
42
- ],
43
- citations: {
44
- 1: 99186,
45
- },
46
- parentMessageId: 6427,
47
- alternateAssistantID: null,
48
- }}
49
- value={['[1]']}
50
- link="https://www.example.com"
51
- />
52
- </MemoryRouter>
53
- </Provider>,
54
- );
55
- const json = component.toJSON();
56
- expect(json).toMatchSnapshot();
57
- });
58
- });
59
-
60
- describe('Citation', () => {
61
- it('should render the component without link', () => {
62
- const store = mockStore({
63
- userSession: { token: '1234' },
64
- intl: {
65
- locale: 'en',
66
- messages: {},
67
- },
68
- });
69
-
70
- const component = renderer.create(
71
- <Provider store={store}>
72
- <MemoryRouter>
73
- <Citation
74
- message={{
75
- messageId: 6428,
76
- message: 'Donec quam felis ultricies nec',
77
- type: 'assistant',
78
- query: 'Pellentesque libero tortor tincidunt et?',
79
- documents: [
80
- {
81
- document_id: 'https://www.example.com',
82
- semantic_identifier: 'Nam ipsum risus rutrum vitae',
83
- link: 'https://www.example.com',
84
- blurb:
85
- 'Nullam nulla eros, ultricies sit amet, nonummy id, imperdiet feugiat, pede. ',
86
- source_type: 'web',
87
- match_highlights: ['', 'Praesent ac sem eget est', ''],
88
- updated_at: null,
89
- db_doc_id: 99186,
90
- },
91
- ],
92
- citations: {
93
- 1: 99186,
94
- },
95
- parentMessageId: 6427,
96
- alternateAssistantID: null,
97
- }}
98
- value={['[1]']}
99
- link=""
100
- />
101
- </MemoryRouter>
102
- </Provider>,
103
- );
104
- const json = component.toJSON();
105
- expect(json).toMatchSnapshot();
106
- });
107
- });