@eeacms/volto-eea-chatbot 1.0.9 → 1.0.11
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/CHANGELOG.md +15 -752
- package/package.json +1 -1
- package/razzle.extend.js +8 -4
- package/src/ChatBlock/ChatBlockView.jsx +26 -2
- package/src/ChatBlock/chat/AIMessage.tsx +5 -1
- package/src/ChatBlock/chat/ChatWindow.tsx +12 -3
- package/src/ChatBlock/components/AutoResizeTextarea.jsx +2 -1
- package/src/ChatBlock/components/QualityCheckToggle.jsx +1 -1
- package/src/ChatBlock/hooks/useChatController.ts +10 -2
- package/src/ChatBlock/index.js +1 -1
- package/src/ChatBlock/packets/renderers/MessageTextRenderer.tsx +8 -0
- package/src/ChatBlock/services/streamingService.ts +30 -26
- package/src/ChatBlock/style.less +3 -1
- package/src/ChatBlock/tests/ChatMessage.test.jsx +75 -0
- package/src/ChatBlock/tests/ClaimModal.test.jsx +136 -0
- package/src/ChatBlock/tests/ClaimSegments.test.jsx +206 -0
- package/src/ChatBlock/tests/CustomToolRenderer.test.jsx +241 -0
- package/src/ChatBlock/tests/FetchToolRenderer.test.jsx +161 -0
- package/src/ChatBlock/tests/ImageToolRenderer.test.jsx +178 -0
- package/src/ChatBlock/tests/MessageTextRenderer.test.jsx +227 -0
- package/src/ChatBlock/tests/MultiToolRenderer.test.jsx +134 -0
- package/src/ChatBlock/tests/ReasoningRenderer.test.jsx +163 -0
- package/src/ChatBlock/tests/RenderClaimView.test.jsx +191 -0
- package/src/ChatBlock/tests/RendererComponent.test.jsx +139 -0
- package/src/ChatBlock/tests/SearchToolRenderer.test.jsx +295 -0
- package/src/ChatBlock/tests/SourceChip.test.jsx +108 -0
- package/src/ChatBlock/tests/UserActionsToolbar.test.jsx +135 -0
- package/src/ChatBlock/tests/UserMessage.test.jsx +83 -0
- package/src/ChatBlock/tests/WebResultIcon.test.jsx +61 -0
- package/src/ChatBlock/tests/citations.test.js +114 -0
- package/src/ChatBlock/tests/messageProcessor.test.jsx +285 -1
- package/src/ChatBlock/tests/packetUtils.test.js +158 -0
- package/src/ChatBlock/tests/streamingService.test.js +467 -0
- package/src/ChatBlock/tests/useChatController.test.jsx +268 -0
- package/src/ChatBlock/tests/useChatStreaming.test.jsx +163 -0
- package/src/ChatBlock/tests/useMarked.test.jsx +107 -0
- package/src/ChatBlock/tests/useQualityMarkers.test.jsx +150 -0
- package/src/ChatBlock/tests/useScrollonStream.test.jsx +121 -0
- package/src/ChatBlock/tests/utils.test.jsx +241 -0
- package/src/ChatBlock/tests/withOnyxData.test.jsx +81 -0
- package/src/ChatBlock/utils/citations.ts +1 -1
- package/src/halloumi/generative.js +1 -0
- package/src/halloumi/generative.test.js +278 -0
- package/src/halloumi/middleware.test.js +69 -0
- package/src/index.js +1 -0
- package/src/middleware.js +21 -13
- package/src/middleware.test.js +221 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import renderer from 'react-test-renderer';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import '@testing-library/jest-dom/extend-expect';
|
|
5
|
+
import { RenderClaimView } from '../components/markdown/RenderClaimView';
|
|
6
|
+
|
|
7
|
+
describe('RenderClaimView', () => {
|
|
8
|
+
const createRef = () => ({ current: {} });
|
|
9
|
+
|
|
10
|
+
it('renders plain text without segments', () => {
|
|
11
|
+
const props = {
|
|
12
|
+
value: 'This is plain text without any segments.',
|
|
13
|
+
segments: [],
|
|
14
|
+
sourceStartIndex: 0,
|
|
15
|
+
visibleSegmentId: null,
|
|
16
|
+
segmentContainerRef: createRef(),
|
|
17
|
+
spanRefs: createRef(),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const component = renderer.create(<RenderClaimView {...props} />);
|
|
21
|
+
const json = component.toJSON();
|
|
22
|
+
expect(json).toMatchSnapshot();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('renders text with a single segment', () => {
|
|
26
|
+
const props = {
|
|
27
|
+
value: 'This is text with a segment here.',
|
|
28
|
+
segments: [
|
|
29
|
+
{
|
|
30
|
+
id: 1,
|
|
31
|
+
startOffset: 20,
|
|
32
|
+
endOffset: 27,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
sourceStartIndex: 0,
|
|
36
|
+
visibleSegmentId: null,
|
|
37
|
+
segmentContainerRef: createRef(),
|
|
38
|
+
spanRefs: createRef(),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const component = renderer.create(<RenderClaimView {...props} />);
|
|
42
|
+
const json = component.toJSON();
|
|
43
|
+
expect(json).toMatchSnapshot();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('renders text with multiple segments', () => {
|
|
47
|
+
const props = {
|
|
48
|
+
value: 'First segment and second segment here.',
|
|
49
|
+
segments: [
|
|
50
|
+
{ id: 1, startOffset: 0, endOffset: 13 },
|
|
51
|
+
{ id: 2, startOffset: 18, endOffset: 32 },
|
|
52
|
+
],
|
|
53
|
+
sourceStartIndex: 0,
|
|
54
|
+
visibleSegmentId: null,
|
|
55
|
+
segmentContainerRef: createRef(),
|
|
56
|
+
spanRefs: createRef(),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const component = renderer.create(<RenderClaimView {...props} />);
|
|
60
|
+
const json = component.toJSON();
|
|
61
|
+
expect(json).toMatchSnapshot();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('highlights visible segment', () => {
|
|
65
|
+
const props = {
|
|
66
|
+
value: 'This is text with a segment here.',
|
|
67
|
+
segments: [
|
|
68
|
+
{
|
|
69
|
+
id: 1,
|
|
70
|
+
startOffset: 20,
|
|
71
|
+
endOffset: 27,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
sourceStartIndex: 0,
|
|
75
|
+
visibleSegmentId: 1,
|
|
76
|
+
segmentContainerRef: createRef(),
|
|
77
|
+
spanRefs: createRef(),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
render(<RenderClaimView {...props} />);
|
|
81
|
+
const segment = document.querySelector('.citation-segment.active');
|
|
82
|
+
expect(segment).toBeInTheDocument();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('handles segments with sourceStartIndex offset', () => {
|
|
86
|
+
const props = {
|
|
87
|
+
value: 'text with segment.',
|
|
88
|
+
segments: [
|
|
89
|
+
{
|
|
90
|
+
id: 1,
|
|
91
|
+
startOffset: 110,
|
|
92
|
+
endOffset: 117,
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
sourceStartIndex: 100,
|
|
96
|
+
visibleSegmentId: null,
|
|
97
|
+
segmentContainerRef: createRef(),
|
|
98
|
+
spanRefs: createRef(),
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const component = renderer.create(<RenderClaimView {...props} />);
|
|
102
|
+
const json = component.toJSON();
|
|
103
|
+
expect(json).toMatchSnapshot();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('handles text with newlines', () => {
|
|
107
|
+
const props = {
|
|
108
|
+
value: 'Line one\nLine two\nLine three',
|
|
109
|
+
segments: [],
|
|
110
|
+
sourceStartIndex: 0,
|
|
111
|
+
visibleSegmentId: null,
|
|
112
|
+
segmentContainerRef: createRef(),
|
|
113
|
+
spanRefs: createRef(),
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const component = renderer.create(<RenderClaimView {...props} />);
|
|
117
|
+
const json = component.toJSON();
|
|
118
|
+
expect(json).toMatchSnapshot();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('handles segment ending with newline', () => {
|
|
122
|
+
const props = {
|
|
123
|
+
value: 'Segment text\nMore text',
|
|
124
|
+
segments: [
|
|
125
|
+
{
|
|
126
|
+
id: 1,
|
|
127
|
+
startOffset: 0,
|
|
128
|
+
endOffset: 13,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
sourceStartIndex: 0,
|
|
132
|
+
visibleSegmentId: null,
|
|
133
|
+
segmentContainerRef: createRef(),
|
|
134
|
+
spanRefs: createRef(),
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const component = renderer.create(<RenderClaimView {...props} />);
|
|
138
|
+
const json = component.toJSON();
|
|
139
|
+
expect(json).toMatchSnapshot();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('filters out DOCUMENT and Source lines', () => {
|
|
143
|
+
const props = {
|
|
144
|
+
value: 'DOCUMENT 1\nActual content\nSource: test',
|
|
145
|
+
segments: [],
|
|
146
|
+
sourceStartIndex: 0,
|
|
147
|
+
visibleSegmentId: null,
|
|
148
|
+
segmentContainerRef: createRef(),
|
|
149
|
+
spanRefs: createRef(),
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
render(<RenderClaimView {...props} />);
|
|
153
|
+
expect(screen.queryByText(/DOCUMENT 1/)).not.toBeInTheDocument();
|
|
154
|
+
expect(screen.queryByText(/Source: test/)).not.toBeInTheDocument();
|
|
155
|
+
expect(screen.getByText('Actual content')).toBeInTheDocument();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('handles empty segments array', () => {
|
|
159
|
+
const props = {
|
|
160
|
+
value: 'Some text content',
|
|
161
|
+
segments: [],
|
|
162
|
+
sourceStartIndex: 0,
|
|
163
|
+
visibleSegmentId: null,
|
|
164
|
+
segmentContainerRef: createRef(),
|
|
165
|
+
spanRefs: createRef(),
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const component = renderer.create(<RenderClaimView {...props} />);
|
|
169
|
+
const json = component.toJSON();
|
|
170
|
+
expect(json).toMatchSnapshot();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('sorts segments by startOffset', () => {
|
|
174
|
+
const props = {
|
|
175
|
+
value: 'AAAA BBBB CCCC',
|
|
176
|
+
segments: [
|
|
177
|
+
{ id: 2, startOffset: 5, endOffset: 9 },
|
|
178
|
+
{ id: 1, startOffset: 0, endOffset: 4 },
|
|
179
|
+
{ id: 3, startOffset: 10, endOffset: 14 },
|
|
180
|
+
],
|
|
181
|
+
sourceStartIndex: 0,
|
|
182
|
+
visibleSegmentId: null,
|
|
183
|
+
segmentContainerRef: createRef(),
|
|
184
|
+
spanRefs: createRef(),
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const component = renderer.create(<RenderClaimView {...props} />);
|
|
188
|
+
const json = component.toJSON();
|
|
189
|
+
expect(json).toMatchSnapshot();
|
|
190
|
+
});
|
|
191
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import renderer from 'react-test-renderer';
|
|
3
|
+
import { findRenderer, RendererComponent } from '../packets/RendererComponent';
|
|
4
|
+
import { PacketType } from '../types/streamingModels';
|
|
5
|
+
|
|
6
|
+
// Mock loadable
|
|
7
|
+
jest.mock('@loadable/component', () => {
|
|
8
|
+
const loadable = (loader) => {
|
|
9
|
+
const MockComponent = (props) => (
|
|
10
|
+
<div data-testid="loadable">{props.children}</div>
|
|
11
|
+
);
|
|
12
|
+
return MockComponent;
|
|
13
|
+
};
|
|
14
|
+
loadable.lib = () => {
|
|
15
|
+
const MockComponent = ({ children }) =>
|
|
16
|
+
children ? children({ default: {} }) : null;
|
|
17
|
+
return MockComponent;
|
|
18
|
+
};
|
|
19
|
+
return { __esModule: true, default: loadable };
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('findRenderer', () => {
|
|
23
|
+
it('returns MessageTextRenderer for chat packets', () => {
|
|
24
|
+
const result = findRenderer({
|
|
25
|
+
packets: [{ ind: 1, obj: { type: PacketType.MESSAGE_START } }],
|
|
26
|
+
});
|
|
27
|
+
expect(result).toBeDefined();
|
|
28
|
+
expect(result.name || result.displayName || '').toBeTruthy();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('returns MessageTextRenderer for MESSAGE_DELTA', () => {
|
|
32
|
+
const result = findRenderer({
|
|
33
|
+
packets: [{ ind: 1, obj: { type: PacketType.MESSAGE_DELTA } }],
|
|
34
|
+
});
|
|
35
|
+
expect(result).toBeDefined();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('returns MessageTextRenderer for MESSAGE_END', () => {
|
|
39
|
+
const result = findRenderer({
|
|
40
|
+
packets: [{ ind: 1, obj: { type: PacketType.MESSAGE_END } }],
|
|
41
|
+
});
|
|
42
|
+
expect(result).toBeDefined();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('returns SearchToolRenderer for search packets', () => {
|
|
46
|
+
const result = findRenderer({
|
|
47
|
+
packets: [{ ind: 1, obj: { type: PacketType.SEARCH_TOOL_START } }],
|
|
48
|
+
});
|
|
49
|
+
expect(result).toBeDefined();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('returns ImageToolRenderer for image packets', () => {
|
|
53
|
+
const result = findRenderer({
|
|
54
|
+
packets: [
|
|
55
|
+
{ ind: 1, obj: { type: PacketType.IMAGE_GENERATION_TOOL_START } },
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
expect(result).toBeDefined();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('returns CustomToolRenderer for custom tool packets', () => {
|
|
62
|
+
const result = findRenderer({
|
|
63
|
+
packets: [{ ind: 1, obj: { type: PacketType.CUSTOM_TOOL_START } }],
|
|
64
|
+
});
|
|
65
|
+
expect(result).toBeDefined();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('returns FetchToolRenderer for fetch tool packets', () => {
|
|
69
|
+
const result = findRenderer({
|
|
70
|
+
packets: [{ ind: 1, obj: { type: PacketType.FETCH_TOOL_START } }],
|
|
71
|
+
});
|
|
72
|
+
expect(result).toBeDefined();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('returns ReasoningRenderer for reasoning packets', () => {
|
|
76
|
+
const result = findRenderer({
|
|
77
|
+
packets: [{ ind: 1, obj: { type: PacketType.REASONING_START } }],
|
|
78
|
+
});
|
|
79
|
+
expect(result).toBeDefined();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('returns ReasoningRenderer for REASONING_DELTA', () => {
|
|
83
|
+
const result = findRenderer({
|
|
84
|
+
packets: [{ ind: 1, obj: { type: PacketType.REASONING_DELTA } }],
|
|
85
|
+
});
|
|
86
|
+
expect(result).toBeDefined();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('returns null for unknown packet types', () => {
|
|
90
|
+
const result = findRenderer({
|
|
91
|
+
packets: [{ ind: 1, obj: { type: 'unknown_type' } }],
|
|
92
|
+
});
|
|
93
|
+
expect(result).toBeNull();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('returns null for empty packets', () => {
|
|
97
|
+
const result = findRenderer({ packets: [] });
|
|
98
|
+
expect(result).toBeNull();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('RendererComponent', () => {
|
|
103
|
+
const childRenderer = (result) => (
|
|
104
|
+
<div>
|
|
105
|
+
<span>{result.status}</span>
|
|
106
|
+
<span>{result.content}</span>
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
it('renders fallback for unrecognized packets', () => {
|
|
111
|
+
const component = renderer.create(
|
|
112
|
+
<RendererComponent
|
|
113
|
+
packets={[{ ind: 1, obj: { type: 'unknown_type' } }]}
|
|
114
|
+
onComplete={jest.fn()}
|
|
115
|
+
animate={false}
|
|
116
|
+
stopPacketSeen={false}
|
|
117
|
+
libs={{ remarkGfm: { default: [] } }}
|
|
118
|
+
>
|
|
119
|
+
{childRenderer}
|
|
120
|
+
</RendererComponent>,
|
|
121
|
+
);
|
|
122
|
+
expect(component.toJSON()).toMatchSnapshot();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('renders fallback for empty packets', () => {
|
|
126
|
+
const component = renderer.create(
|
|
127
|
+
<RendererComponent
|
|
128
|
+
packets={[]}
|
|
129
|
+
onComplete={jest.fn()}
|
|
130
|
+
animate={false}
|
|
131
|
+
stopPacketSeen={false}
|
|
132
|
+
libs={{ remarkGfm: { default: [] } }}
|
|
133
|
+
>
|
|
134
|
+
{childRenderer}
|
|
135
|
+
</RendererComponent>,
|
|
136
|
+
);
|
|
137
|
+
expect(component.toJSON()).toMatchSnapshot();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import renderer, { act } from 'react-test-renderer';
|
|
3
|
+
import '@testing-library/jest-dom/extend-expect';
|
|
4
|
+
import { SearchToolRenderer } from '../packets/renderers/SearchToolRenderer';
|
|
5
|
+
import { PacketType } from '../types/streamingModels';
|
|
6
|
+
|
|
7
|
+
describe('SearchToolRenderer', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
jest.useFakeTimers();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
jest.useRealTimers();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const mockChildRenderer = (result) => (
|
|
17
|
+
<div data-testid="renderer-result">
|
|
18
|
+
<div data-testid="status">{result.status}</div>
|
|
19
|
+
<div data-testid="content">{result.content}</div>
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
it('renders searching state for internal documents', () => {
|
|
24
|
+
const props = {
|
|
25
|
+
packets: [
|
|
26
|
+
{
|
|
27
|
+
ind: 1,
|
|
28
|
+
obj: {
|
|
29
|
+
type: PacketType.SEARCH_TOOL_START,
|
|
30
|
+
is_internet_search: false,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
ind: 1,
|
|
35
|
+
obj: {
|
|
36
|
+
type: PacketType.SEARCH_TOOL_DELTA,
|
|
37
|
+
queries: ['test query'],
|
|
38
|
+
documents: null,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
onComplete: jest.fn(),
|
|
43
|
+
animate: false,
|
|
44
|
+
children: mockChildRenderer,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const component = renderer.create(<SearchToolRenderer {...props} />);
|
|
48
|
+
const json = component.toJSON();
|
|
49
|
+
expect(json).toMatchSnapshot();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('renders searching state for internet search', () => {
|
|
53
|
+
const props = {
|
|
54
|
+
packets: [
|
|
55
|
+
{
|
|
56
|
+
ind: 1,
|
|
57
|
+
obj: {
|
|
58
|
+
type: PacketType.SEARCH_TOOL_START,
|
|
59
|
+
is_internet_search: true,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
ind: 1,
|
|
64
|
+
obj: {
|
|
65
|
+
type: PacketType.SEARCH_TOOL_DELTA,
|
|
66
|
+
queries: ['web query'],
|
|
67
|
+
documents: null,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
onComplete: jest.fn(),
|
|
72
|
+
animate: false,
|
|
73
|
+
children: mockChildRenderer,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const component = renderer.create(<SearchToolRenderer {...props} />);
|
|
77
|
+
const json = component.toJSON();
|
|
78
|
+
expect(json).toMatchSnapshot();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('renders with search results', () => {
|
|
82
|
+
const props = {
|
|
83
|
+
packets: [
|
|
84
|
+
{
|
|
85
|
+
ind: 1,
|
|
86
|
+
obj: {
|
|
87
|
+
type: PacketType.SEARCH_TOOL_START,
|
|
88
|
+
is_internet_search: false,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
ind: 1,
|
|
93
|
+
obj: {
|
|
94
|
+
type: PacketType.SEARCH_TOOL_DELTA,
|
|
95
|
+
queries: ['query'],
|
|
96
|
+
documents: [
|
|
97
|
+
{
|
|
98
|
+
document_id: 'doc1',
|
|
99
|
+
semantic_identifier: 'Document 1',
|
|
100
|
+
link: 'https://example.com/1',
|
|
101
|
+
source_type: 'file',
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
{ ind: 1, obj: { type: PacketType.SECTION_END } },
|
|
107
|
+
],
|
|
108
|
+
onComplete: jest.fn(),
|
|
109
|
+
animate: false,
|
|
110
|
+
children: mockChildRenderer,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
let component;
|
|
114
|
+
act(() => {
|
|
115
|
+
component = renderer.create(<SearchToolRenderer {...props} />);
|
|
116
|
+
});
|
|
117
|
+
const json = component.toJSON();
|
|
118
|
+
expect(json).toMatchSnapshot();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('renders empty state when no queries', () => {
|
|
122
|
+
const props = {
|
|
123
|
+
packets: [
|
|
124
|
+
{
|
|
125
|
+
ind: 1,
|
|
126
|
+
obj: {
|
|
127
|
+
type: PacketType.SEARCH_TOOL_START,
|
|
128
|
+
is_internet_search: false,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
onComplete: jest.fn(),
|
|
133
|
+
animate: false,
|
|
134
|
+
children: mockChildRenderer,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const component = renderer.create(<SearchToolRenderer {...props} />);
|
|
138
|
+
const json = component.toJSON();
|
|
139
|
+
expect(json).toMatchSnapshot();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('removes duplicate queries', () => {
|
|
143
|
+
const props = {
|
|
144
|
+
packets: [
|
|
145
|
+
{
|
|
146
|
+
ind: 1,
|
|
147
|
+
obj: {
|
|
148
|
+
type: PacketType.SEARCH_TOOL_START,
|
|
149
|
+
is_internet_search: false,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
ind: 1,
|
|
154
|
+
obj: {
|
|
155
|
+
type: PacketType.SEARCH_TOOL_DELTA,
|
|
156
|
+
queries: ['duplicate', 'unique', 'duplicate'],
|
|
157
|
+
documents: null,
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
onComplete: jest.fn(),
|
|
162
|
+
animate: false,
|
|
163
|
+
children: mockChildRenderer,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const component = renderer.create(<SearchToolRenderer {...props} />);
|
|
167
|
+
const json = component.toJSON();
|
|
168
|
+
expect(json).toMatchSnapshot();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('removes duplicate documents by document_id', () => {
|
|
172
|
+
const props = {
|
|
173
|
+
packets: [
|
|
174
|
+
{
|
|
175
|
+
ind: 1,
|
|
176
|
+
obj: {
|
|
177
|
+
type: PacketType.SEARCH_TOOL_START,
|
|
178
|
+
is_internet_search: false,
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
ind: 1,
|
|
183
|
+
obj: {
|
|
184
|
+
type: PacketType.SEARCH_TOOL_DELTA,
|
|
185
|
+
queries: ['query'],
|
|
186
|
+
documents: [
|
|
187
|
+
{ document_id: 'doc1', semantic_identifier: 'Doc 1' },
|
|
188
|
+
{ document_id: 'doc1', semantic_identifier: 'Doc 1 duplicate' },
|
|
189
|
+
{ document_id: 'doc2', semantic_identifier: 'Doc 2' },
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
{ ind: 1, obj: { type: PacketType.SECTION_END } },
|
|
194
|
+
],
|
|
195
|
+
onComplete: jest.fn(),
|
|
196
|
+
animate: false,
|
|
197
|
+
children: mockChildRenderer,
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
let component;
|
|
201
|
+
act(() => {
|
|
202
|
+
component = renderer.create(<SearchToolRenderer {...props} />);
|
|
203
|
+
});
|
|
204
|
+
const json = component.toJSON();
|
|
205
|
+
expect(json).toMatchSnapshot();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('handles web results with favicon', () => {
|
|
209
|
+
const props = {
|
|
210
|
+
packets: [
|
|
211
|
+
{
|
|
212
|
+
ind: 1,
|
|
213
|
+
obj: {
|
|
214
|
+
type: PacketType.SEARCH_TOOL_START,
|
|
215
|
+
is_internet_search: true,
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
ind: 1,
|
|
220
|
+
obj: {
|
|
221
|
+
type: PacketType.SEARCH_TOOL_DELTA,
|
|
222
|
+
queries: ['web search'],
|
|
223
|
+
documents: [
|
|
224
|
+
{
|
|
225
|
+
document_id: 'web1',
|
|
226
|
+
semantic_identifier: 'Web Result',
|
|
227
|
+
link: 'https://example.com/page',
|
|
228
|
+
source_type: 'web',
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
{ ind: 1, obj: { type: PacketType.SECTION_END } },
|
|
234
|
+
],
|
|
235
|
+
onComplete: jest.fn(),
|
|
236
|
+
animate: false,
|
|
237
|
+
children: mockChildRenderer,
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
let component;
|
|
241
|
+
act(() => {
|
|
242
|
+
component = renderer.create(<SearchToolRenderer {...props} />);
|
|
243
|
+
});
|
|
244
|
+
const json = component.toJSON();
|
|
245
|
+
expect(json).toMatchSnapshot();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('handles empty packets array', () => {
|
|
249
|
+
const props = {
|
|
250
|
+
packets: [],
|
|
251
|
+
onComplete: jest.fn(),
|
|
252
|
+
animate: false,
|
|
253
|
+
children: mockChildRenderer,
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const component = renderer.create(<SearchToolRenderer {...props} />);
|
|
257
|
+
const json = component.toJSON();
|
|
258
|
+
expect(json).toMatchSnapshot();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('calls onComplete after animation delay when animate is true', () => {
|
|
262
|
+
const onComplete = jest.fn();
|
|
263
|
+
const props = {
|
|
264
|
+
packets: [
|
|
265
|
+
{ ind: 1, obj: { type: PacketType.SEARCH_TOOL_START } },
|
|
266
|
+
{
|
|
267
|
+
ind: 1,
|
|
268
|
+
obj: {
|
|
269
|
+
type: PacketType.SEARCH_TOOL_DELTA,
|
|
270
|
+
queries: ['query'],
|
|
271
|
+
documents: [],
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
{ ind: 1, obj: { type: PacketType.SECTION_END } },
|
|
275
|
+
],
|
|
276
|
+
onComplete,
|
|
277
|
+
animate: true,
|
|
278
|
+
children: mockChildRenderer,
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
act(() => {
|
|
282
|
+
renderer.create(<SearchToolRenderer {...props} />);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Initially not called
|
|
286
|
+
expect(onComplete).not.toHaveBeenCalled();
|
|
287
|
+
|
|
288
|
+
// Advance timers for the animation duration
|
|
289
|
+
act(() => {
|
|
290
|
+
jest.advanceTimersByTime(3000);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
expect(onComplete).toHaveBeenCalled();
|
|
294
|
+
});
|
|
295
|
+
});
|