@quidgest/chatbot 0.5.3-dev.0 → 0.5.4
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/dist/components/ChatBot/__tests__/ChatBot.spec.d.ts +1 -0
- package/dist/components/ChatBot/types.d.ts +3 -1
- package/dist/components/ChatBotInput/ChatBotInput.vue.d.ts +3 -3
- package/dist/components/ChatBotInput/index.d.ts +2 -2
- package/dist/components/ChatBotInput/types.d.ts +4 -4
- package/dist/components/ChatBotMessage/__tests__/ChatBotMessage.spec.d.ts +1 -0
- package/dist/components/ChatBotMessage/types.d.ts +3 -2
- package/dist/composables/useChatMessages.d.ts +2 -1
- package/dist/composables/useTexts.d.ts +1 -0
- package/dist/index.js +18 -18
- package/dist/index.mjs +1200 -1165
- package/dist/style.css +1 -1
- package/package.json +2 -1
- package/src/assets/styles/preview-file.scss +70 -0
- package/src/assets/styles/styles.scss +9 -33
- package/src/components/ChatBot/ChatBot.vue +10 -14
- package/src/components/ChatBot/__tests__/ChatBot.spec.ts +52 -0
- package/src/components/ChatBot/__tests__/__snapshots__/ChatBot.spec.ts.snap +39 -0
- package/src/components/ChatBot/types.ts +3 -1
- package/src/components/ChatBotInput/ChatBotInput.vue +81 -74
- package/src/components/ChatBotInput/__tests__/ChatBotInput.spec.ts +29 -42
- package/src/components/ChatBotInput/__tests__/__snapshots__/ChatBotInput.spec.ts.snap +5 -5
- package/src/components/ChatBotInput/index.ts +2 -2
- package/src/components/ChatBotInput/types.ts +4 -4
- package/src/components/ChatBotMessage/ChatBotMessage.vue +31 -3
- package/src/components/ChatBotMessage/ChatBotMessageButtons.vue +1 -0
- package/src/components/ChatBotMessage/__tests__/ChatBotMessage.spec.ts +260 -0
- package/src/components/ChatBotMessage/__tests__/__snapshots__/ChatBotMessage.spec.ts.snap +35 -0
- package/src/components/ChatBotMessage/types.ts +4 -3
- package/src/components/FieldPreview/__tests__/__snapshots__/FieldPreview.spec.ts.snap +1 -1
- package/src/composables/useChatMessages.ts +3 -2
- package/src/composables/useTexts.ts +2 -1
|
@@ -7,13 +7,6 @@ import { describe, expect, it, vi } from 'vitest'
|
|
|
7
7
|
|
|
8
8
|
// Types
|
|
9
9
|
import type { ChatBotInputProps } from '..'
|
|
10
|
-
import type { ComponentPublicInstance } from 'vue'
|
|
11
|
-
|
|
12
|
-
type ChatBotInputVm = ComponentPublicInstance<{
|
|
13
|
-
imageInput: HTMLInputElement | null
|
|
14
|
-
imagePreviewUrl: string
|
|
15
|
-
hasSelectedImage: boolean
|
|
16
|
-
}>
|
|
17
10
|
|
|
18
11
|
describe('ChatBotInput', () => {
|
|
19
12
|
const props: ChatBotInputProps = {
|
|
@@ -81,7 +74,7 @@ describe('ChatBotInput', () => {
|
|
|
81
74
|
await footer.trigger('drop')
|
|
82
75
|
|
|
83
76
|
expect(wrapper.find('img').exists()).toBe(false)
|
|
84
|
-
expect(wrapper.find('.q-
|
|
77
|
+
expect(wrapper.find('.q-chatbot__file-preview').exists()).toBe(false)
|
|
85
78
|
})
|
|
86
79
|
|
|
87
80
|
it('prevents drop if theres is no files', async () => {
|
|
@@ -95,7 +88,7 @@ describe('ChatBotInput', () => {
|
|
|
95
88
|
})
|
|
96
89
|
|
|
97
90
|
expect(wrapper.find('img').exists()).toBe(false)
|
|
98
|
-
expect(wrapper.find('.q-
|
|
91
|
+
expect(wrapper.find('.q-chatbot__file-preview').exists()).toBe(false)
|
|
99
92
|
})
|
|
100
93
|
|
|
101
94
|
it('accepts image files on drop', async () => {
|
|
@@ -113,32 +106,7 @@ describe('ChatBotInput', () => {
|
|
|
113
106
|
})
|
|
114
107
|
|
|
115
108
|
expect(wrapper.find('img').exists()).toBe(true)
|
|
116
|
-
expect(wrapper.find('.q-
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
it('does not drop image if hidden input does not exist', async () => {
|
|
120
|
-
const wrapper = mount(ChatBotInput, {
|
|
121
|
-
props: { ...props }
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
const footer = wrapper.find('.q-chatbot__footer')
|
|
125
|
-
const file = new File(['dummy content'], 'example.png', { type: 'image/png' })
|
|
126
|
-
|
|
127
|
-
// Manually set imageInput to null to simulate the missing input
|
|
128
|
-
// In reality, this should not happen as the input is always rendered but hidden
|
|
129
|
-
const vm = wrapper.vm as ChatBotInputVm
|
|
130
|
-
vm.imageInput = null
|
|
131
|
-
|
|
132
|
-
// Trigger the drop event
|
|
133
|
-
await footer.trigger('drop', {
|
|
134
|
-
dataTransfer: {
|
|
135
|
-
files: [file]
|
|
136
|
-
}
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
// Assert that no image preview is created
|
|
140
|
-
expect(wrapper.find('img').exists()).toBe(false)
|
|
141
|
-
expect(wrapper.find('.q-chatbot__image-preview').exists()).toBe(false)
|
|
109
|
+
expect(wrapper.find('.q-chatbot__file-preview').exists()).toBe(true)
|
|
142
110
|
})
|
|
143
111
|
|
|
144
112
|
it('does not send empty prompts', async () => {
|
|
@@ -222,7 +190,7 @@ describe('ChatBotInput', () => {
|
|
|
222
190
|
expect(wrapper.emitted()['send-message'][0]).toEqual([
|
|
223
191
|
'Hello',
|
|
224
192
|
{
|
|
225
|
-
|
|
193
|
+
fileData: file,
|
|
226
194
|
previewUrl: expect.stringContaining('blob:')
|
|
227
195
|
}
|
|
228
196
|
])
|
|
@@ -244,14 +212,14 @@ describe('ChatBotInput', () => {
|
|
|
244
212
|
})
|
|
245
213
|
|
|
246
214
|
expect(wrapper.find('img').exists()).toBe(true)
|
|
247
|
-
expect(wrapper.find('.q-
|
|
215
|
+
expect(wrapper.find('.q-chatbot__file-preview').exists()).toBe(true)
|
|
248
216
|
|
|
249
217
|
// Simulate clicking the remove image button
|
|
250
|
-
const removeButton = wrapper.find('.q-chatbot__remove-
|
|
218
|
+
const removeButton = wrapper.find('.q-chatbot__remove-file')
|
|
251
219
|
await removeButton.trigger('click')
|
|
252
220
|
|
|
253
221
|
expect(wrapper.find('img').exists()).toBe(false)
|
|
254
|
-
expect(wrapper.find('.q-
|
|
222
|
+
expect(wrapper.find('.q-chatbot__file-preview').exists()).toBe(false)
|
|
255
223
|
})
|
|
256
224
|
|
|
257
225
|
it('triggers click on the hidden input when upload button is clicked', async () => {
|
|
@@ -260,7 +228,7 @@ describe('ChatBotInput', () => {
|
|
|
260
228
|
})
|
|
261
229
|
|
|
262
230
|
const uploadButton = wrapper.find('.q-chatbot__upload')
|
|
263
|
-
const fileInput = wrapper.find<HTMLInputElement>('#
|
|
231
|
+
const fileInput = wrapper.find<HTMLInputElement>('#file-upload')
|
|
264
232
|
|
|
265
233
|
// Mock the click method on the file input
|
|
266
234
|
const clickMock = vi.fn()
|
|
@@ -276,7 +244,7 @@ describe('ChatBotInput', () => {
|
|
|
276
244
|
const wrapper = mount(ChatBotInput, {
|
|
277
245
|
props: { ...props }
|
|
278
246
|
})
|
|
279
|
-
const fileInput = wrapper.find<HTMLInputElement>('#
|
|
247
|
+
const fileInput = wrapper.find<HTMLInputElement>('#file-upload')
|
|
280
248
|
const file = new File(['dummy content'], 'example.png', { type: 'image/png' })
|
|
281
249
|
|
|
282
250
|
// Simulate selecting a file
|
|
@@ -287,6 +255,25 @@ describe('ChatBotInput', () => {
|
|
|
287
255
|
await fileInput.trigger('change')
|
|
288
256
|
|
|
289
257
|
expect(wrapper.find('img').exists()).toBe(true)
|
|
290
|
-
expect(wrapper.find('.q-
|
|
258
|
+
expect(wrapper.find('.q-chatbot__file-preview').exists()).toBe(true)
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
it('renders a document preview for non-image files', async () => {
|
|
262
|
+
const wrapper = mount(ChatBotInput, {
|
|
263
|
+
props: { ...props }
|
|
264
|
+
})
|
|
265
|
+
const fileInput = wrapper.find<HTMLInputElement>('#file-upload')
|
|
266
|
+
const file = new File(['dummy content'], 'example.pdf', { type: 'application/pdf' })
|
|
267
|
+
|
|
268
|
+
// Simulate selecting a file
|
|
269
|
+
const dataTransfer = new DataTransfer()
|
|
270
|
+
dataTransfer.items.add(file)
|
|
271
|
+
fileInput.element.files = dataTransfer.files
|
|
272
|
+
|
|
273
|
+
await fileInput.trigger('change')
|
|
274
|
+
|
|
275
|
+
expect(wrapper.find('img').exists()).toBe(false)
|
|
276
|
+
expect(wrapper.find('.q-chatbot__file-name').exists()).toBe(true)
|
|
277
|
+
expect(wrapper.find('.q-chatbot__file-name').text()).toBe(file.name)
|
|
291
278
|
})
|
|
292
279
|
})
|
|
@@ -15,11 +15,11 @@ exports[`ChatBotInput > renders correctly with default props 1`] = `
|
|
|
15
15
|
<!--v-if-->
|
|
16
16
|
</div>
|
|
17
17
|
</div>
|
|
18
|
+
<div class="q-chatbot__send-container"><button type="button" class="q-button q-button--outlined q-button--primary q-chatbot__upload" title="Upload Image">
|
|
19
|
+
<!--v-if--><span class="q-button__content"><span data-test="upload"></span> </span>
|
|
20
|
+
</button><!-- Hidden file input --><input id="file-upload" type="file" accept=".png,.jpeg,.jpg,.svg,.webp,.pdf,.doc,.docx" class="hidden-input"><button type="button" class="q-button q-button--bold q-button--primary q-chatbot__send" disabled="" title="Send message" readonly="">
|
|
21
|
+
<!--v-if--><span class="q-button__content"><span data-test="send"></span> </span>
|
|
22
|
+
</button></div>
|
|
18
23
|
</div>
|
|
19
|
-
<div class="q-chatbot__send-container"><button type="button" class="q-button q-button--outlined q-button--primary q-chatbot__upload" title="Upload Image">
|
|
20
|
-
<!--v-if--><span class="q-button__content"><span data-test="upload"></span> </span>
|
|
21
|
-
</button><!-- Hidden file input --><input id="image-upload" type="file" accept=".png, .jpeg, .jpg, .svg, .webp" class="hidden-input"><button type="button" class="q-button q-button--bold q-button--primary q-chatbot__send" disabled="" title="Send message" readonly="">
|
|
22
|
-
<!--v-if--><span class="q-button__content"><span data-test="send"></span> </span>
|
|
23
|
-
</button></div>
|
|
24
24
|
</div>"
|
|
25
25
|
`;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ChatBotInput from './ChatBotInput.vue'
|
|
2
|
-
import type { ChatBotInputProps,
|
|
2
|
+
import type { ChatBotInputProps, ChatBotFile } from './types'
|
|
3
3
|
|
|
4
4
|
export { ChatBotInput }
|
|
5
|
-
export type { ChatBotInputProps,
|
|
5
|
+
export type { ChatBotInputProps, ChatBotFile }
|
|
@@ -20,14 +20,14 @@ export type ChatBotInputProps = {
|
|
|
20
20
|
agentId?: string
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
export type
|
|
23
|
+
export type ChatBotFile = {
|
|
24
24
|
/**
|
|
25
|
-
* The
|
|
25
|
+
* The preview URL for the file (if it's an image)
|
|
26
26
|
*/
|
|
27
|
-
previewUrl
|
|
27
|
+
previewUrl?: string
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* The file object
|
|
31
31
|
*/
|
|
32
|
-
|
|
32
|
+
fileData: File
|
|
33
33
|
}
|
|
@@ -8,12 +8,29 @@
|
|
|
8
8
|
|
|
9
9
|
<div class="q-chatbot__message-wrapper">
|
|
10
10
|
<div
|
|
11
|
-
v-if="
|
|
11
|
+
v-if="isImageFile"
|
|
12
12
|
class="q-chatbot__image-preview">
|
|
13
13
|
<img
|
|
14
|
-
:src="props.
|
|
14
|
+
:src="props.file?.previewUrl"
|
|
15
15
|
:alt="texts.imagePreview" />
|
|
16
16
|
</div>
|
|
17
|
+
<div
|
|
18
|
+
v-else-if="props.file?.fileData && !isImageFile"
|
|
19
|
+
class="q-chatbot__file-preview-container">
|
|
20
|
+
<div class="q-chatbot__file-icon-container">
|
|
21
|
+
<q-icon
|
|
22
|
+
icon="file"
|
|
23
|
+
class="q-chatbot__file-icon" />
|
|
24
|
+
</div>
|
|
25
|
+
<div class="q-chatbot__file-info">
|
|
26
|
+
<span class="q-chatbot__file-name">
|
|
27
|
+
{{ props.file?.fileData.name }}
|
|
28
|
+
</span>
|
|
29
|
+
<span class="q-chatbot__file-extension">
|
|
30
|
+
{{ fileExtension }}
|
|
31
|
+
</span>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
17
34
|
<div class="q-chatbot__message">
|
|
18
35
|
<pulse-dots v-if="loading" />
|
|
19
36
|
<template v-else-if="props.sender === 'bot' && props.fields.length > 0">
|
|
@@ -35,7 +52,7 @@
|
|
|
35
52
|
:source="props.message || ''" />
|
|
36
53
|
<div
|
|
37
54
|
v-else
|
|
38
|
-
class="q-chatbot__text">
|
|
55
|
+
class="q-chatbot__text q-chatbot__user-text">
|
|
39
56
|
{{ props.message }}
|
|
40
57
|
</div>
|
|
41
58
|
</template>
|
|
@@ -96,10 +113,21 @@
|
|
|
96
113
|
)
|
|
97
114
|
})
|
|
98
115
|
|
|
116
|
+
const isImageFile = computed(() => {
|
|
117
|
+
return props.file?.fileData.type.startsWith('image/') ?? false
|
|
118
|
+
})
|
|
119
|
+
|
|
99
120
|
const messageImage = computed(() =>
|
|
100
121
|
props.sender === 'bot' ? props.chatbotImage : props.userImage
|
|
101
122
|
)
|
|
102
123
|
|
|
124
|
+
const fileExtension = computed(() => {
|
|
125
|
+
if (!props.file?.fileData) return ''
|
|
126
|
+
const ext = props.file.fileData.name.split('.').pop()?.toUpperCase()
|
|
127
|
+
|
|
128
|
+
return ext ?? ''
|
|
129
|
+
})
|
|
130
|
+
|
|
103
131
|
function copyResponse() {
|
|
104
132
|
if (!props.message) return
|
|
105
133
|
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
// Components
|
|
2
|
+
import { ChatBotMessage, ChatBotMessageButtons } from '..'
|
|
3
|
+
import PulseDots from '@/components/PulseDots/PulseDots.vue'
|
|
4
|
+
import { FieldPreview } from '@/components/FieldPreview/'
|
|
5
|
+
import { MarkdownRender } from '@/components/MarkdownRender/'
|
|
6
|
+
|
|
7
|
+
// Utils
|
|
8
|
+
import { mount } from '@vue/test-utils'
|
|
9
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
|
10
|
+
import { parseFieldValue } from '@/utils/parseFieldValue'
|
|
11
|
+
|
|
12
|
+
// Types
|
|
13
|
+
import type { ChatBotMessageProps } from '..'
|
|
14
|
+
|
|
15
|
+
// Composable
|
|
16
|
+
import { useChatMessages } from '@/composables/useChatMessages'
|
|
17
|
+
import { ChatBotFile } from '@/components/ChatBotInput'
|
|
18
|
+
|
|
19
|
+
describe('ChatBotMessage', () => {
|
|
20
|
+
const fixedDate = new Date('2025-01-01T11:47:00')
|
|
21
|
+
const { clearMessages, addChatMessage } = useChatMessages()
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
vi.useFakeTimers()
|
|
25
|
+
vi.setSystemTime(fixedDate)
|
|
26
|
+
|
|
27
|
+
navigator.clipboard.writeText('')
|
|
28
|
+
|
|
29
|
+
clearMessages()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
vi.useRealTimers()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const props: ChatBotMessageProps = {
|
|
37
|
+
sender: 'bot',
|
|
38
|
+
message: 'Hello, this is a test message.',
|
|
39
|
+
date: fixedDate,
|
|
40
|
+
loading: false,
|
|
41
|
+
dateFormat: 'HH:mm',
|
|
42
|
+
apiEndpoint: 'https://api.example.com/chat',
|
|
43
|
+
fields: [],
|
|
44
|
+
userImage: '',
|
|
45
|
+
chatbotImage: '',
|
|
46
|
+
file: undefined
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
it('renders the component with default props', () => {
|
|
50
|
+
const wrapper = mount(ChatBotMessage, { props })
|
|
51
|
+
expect(wrapper.html()).toMatchSnapshot()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('renders the image preview when imagePreviewUrl is provided', () => {
|
|
55
|
+
const file: ChatBotFile = {
|
|
56
|
+
fileData: new File(['dummy content'], 'example.png', { type: 'image/png' }),
|
|
57
|
+
previewUrl: 'tottaly-legit-image.png'
|
|
58
|
+
}
|
|
59
|
+
const wrapper = mount(ChatBotMessage, {
|
|
60
|
+
props: { ...props, file }
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const img = wrapper.find('.q-chatbot__image-preview img')
|
|
64
|
+
expect(img.exists()).toBe(true)
|
|
65
|
+
expect(img.attributes('src')).toBe('tottaly-legit-image.png')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('shows loading state when loading is true', () => {
|
|
69
|
+
const wrapper = mount(ChatBotMessage, {
|
|
70
|
+
props: { ...props, loading: true }
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const pulseDots = wrapper.findComponent(PulseDots)
|
|
74
|
+
expect(pulseDots.exists()).toBe(true)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('renders the field preview component when fields are provided and the sender is bot', () => {
|
|
78
|
+
const wrapper = mount(ChatBotMessage, {
|
|
79
|
+
props: {
|
|
80
|
+
...props,
|
|
81
|
+
sender: 'bot',
|
|
82
|
+
fields: [
|
|
83
|
+
{
|
|
84
|
+
id: 'field-1',
|
|
85
|
+
type: 'text',
|
|
86
|
+
text: 'Sample Field',
|
|
87
|
+
name: 'This is a sample field value'
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const fieldPreview = wrapper.findComponent(FieldPreview)
|
|
94
|
+
expect(fieldPreview.exists()).toBe(true)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('renders a normal when no fields are provided and the sender is user', () => {
|
|
98
|
+
const wrapper = mount(ChatBotMessage, {
|
|
99
|
+
props: { ...props, sender: 'user' }
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const fieldPreview = wrapper.findComponent(FieldPreview)
|
|
103
|
+
expect(fieldPreview.exists()).toBe(false)
|
|
104
|
+
|
|
105
|
+
const markdownRender = wrapper.findComponent(MarkdownRender)
|
|
106
|
+
expect(markdownRender.exists()).toBe(false)
|
|
107
|
+
|
|
108
|
+
const messageDiv = wrapper.find('.q-chatbot__user-text')
|
|
109
|
+
expect(messageDiv.exists()).toBe(true)
|
|
110
|
+
expect(messageDiv.text()).toBe(props.message)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('renders markdown-render when no fields are provided', () => {
|
|
114
|
+
const wrapper = mount(ChatBotMessage, { props })
|
|
115
|
+
|
|
116
|
+
const markdownRender = wrapper.findComponent(MarkdownRender)
|
|
117
|
+
expect(markdownRender.exists()).toBe(true)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('adds the text to the clipboard when copy event is emmited', async () => {
|
|
121
|
+
const wrapper = mount(ChatBotMessage, { props })
|
|
122
|
+
|
|
123
|
+
const messageButtons = wrapper.findComponent(ChatBotMessageButtons)
|
|
124
|
+
const copyButton = messageButtons.find('.q-chatbot__copy-button')
|
|
125
|
+
|
|
126
|
+
await copyButton.trigger('click')
|
|
127
|
+
const clipboardText = await navigator.clipboard.readText()
|
|
128
|
+
|
|
129
|
+
expect(navigator.clipboard).toBeDefined()
|
|
130
|
+
expect(clipboardText).toBe(props.message)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('does not add the text to the clipboard if message is empty', async () => {
|
|
134
|
+
const wrapper = mount(ChatBotMessage, { props: { ...props, message: '' } })
|
|
135
|
+
const messageButtons = wrapper.findComponent(ChatBotMessageButtons)
|
|
136
|
+
const copyButton = messageButtons.find('.q-chatbot__copy-button')
|
|
137
|
+
|
|
138
|
+
await copyButton.trigger('click')
|
|
139
|
+
const clipboardText = await navigator.clipboard.readText()
|
|
140
|
+
|
|
141
|
+
expect(navigator.clipboard).toBeDefined()
|
|
142
|
+
expect(clipboardText).toBe('')
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('catchs errors when trying to copy to clipboard', async () => {
|
|
146
|
+
const wrapper = mount(ChatBotMessage, { props })
|
|
147
|
+
const messageButtons = wrapper.findComponent(ChatBotMessageButtons)
|
|
148
|
+
const copyButton = messageButtons.find('.q-chatbot__copy-button')
|
|
149
|
+
|
|
150
|
+
vi.spyOn(navigator.clipboard, 'writeText').mockRejectedValueOnce(
|
|
151
|
+
new Error('Clipboard error')
|
|
152
|
+
)
|
|
153
|
+
// Suppress console.error for this test
|
|
154
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
155
|
+
|
|
156
|
+
await copyButton.trigger('click')
|
|
157
|
+
const clipboardText = await navigator.clipboard.readText()
|
|
158
|
+
|
|
159
|
+
expect(navigator.clipboard).toBeDefined()
|
|
160
|
+
expect(clipboardText).toBe('')
|
|
161
|
+
consoleErrorSpy.mockRestore()
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('emits apply-fields event with fields when apply all button is clicked', async () => {
|
|
165
|
+
const testFields = [
|
|
166
|
+
{
|
|
167
|
+
id: 'field-1',
|
|
168
|
+
type: 'text',
|
|
169
|
+
text: 'Sample Field',
|
|
170
|
+
name: 'This is a sample field value'
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: 'field-2',
|
|
174
|
+
type: 'number',
|
|
175
|
+
text: 'Number Field',
|
|
176
|
+
name: '42'
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
addChatMessage('Test message 1', 'bot').fields = testFields
|
|
180
|
+
|
|
181
|
+
const wrapper = mount(ChatBotMessage, {
|
|
182
|
+
props: { ...props, sender: 'bot', fields: testFields }
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
const messageButtons = wrapper.findComponent(ChatBotMessageButtons)
|
|
186
|
+
const applyAllButton = messageButtons.find('.q-chatbot__apply-all-button')
|
|
187
|
+
|
|
188
|
+
expect(applyAllButton.exists()).toBe(true)
|
|
189
|
+
await applyAllButton.trigger('click')
|
|
190
|
+
|
|
191
|
+
const fieldsToApply = testFields.map((field) => {
|
|
192
|
+
return {
|
|
193
|
+
id: field.id,
|
|
194
|
+
text: parseFieldValue(field.type, field.text)
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
expect(wrapper.emitted('apply-fields')).toBeTruthy()
|
|
199
|
+
expect(wrapper.emitted('apply-fields')?.[0]).toEqual([fieldsToApply])
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('emits apply-field when a single field is applied', async () => {
|
|
203
|
+
const testFields = [
|
|
204
|
+
{
|
|
205
|
+
id: 'field-1',
|
|
206
|
+
type: 'text',
|
|
207
|
+
text: 'Sample Field',
|
|
208
|
+
name: 'This is a sample field value'
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
|
|
212
|
+
addChatMessage('Test message 1', 'bot').fields = testFields
|
|
213
|
+
|
|
214
|
+
const wrapper = mount(ChatBotMessage, {
|
|
215
|
+
props: { ...props, sender: 'bot', fields: testFields }
|
|
216
|
+
})
|
|
217
|
+
const fieldPreview = wrapper.findComponent(FieldPreview)
|
|
218
|
+
expect(fieldPreview.exists()).toBe(true)
|
|
219
|
+
|
|
220
|
+
const applyButton = fieldPreview.find("[data-testid='apply-button']")
|
|
221
|
+
expect(applyButton.exists()).toBe(true)
|
|
222
|
+
|
|
223
|
+
await applyButton.trigger('click')
|
|
224
|
+
|
|
225
|
+
const fieldToApply = [
|
|
226
|
+
{
|
|
227
|
+
id: testFields[0].id,
|
|
228
|
+
text: parseFieldValue(testFields[0].type, testFields[0].text)
|
|
229
|
+
}
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
expect(wrapper.emitted('apply-fields')).toBeTruthy()
|
|
233
|
+
expect(wrapper.emitted('apply-fields')?.[0]).toEqual([fieldToApply])
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
it('renders a different preview when the file is not an image', async () => {
|
|
237
|
+
const file: ChatBotFile = {
|
|
238
|
+
fileData: new File(['dummy content'], 'example.pdf', { type: 'application/pdf' }),
|
|
239
|
+
previewUrl: ''
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const wrapper = mount(ChatBotMessage, {
|
|
243
|
+
props: { ...props, file }
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
const img = wrapper.find('.q-chatbot__image-preview img')
|
|
247
|
+
expect(img.exists()).toBe(false)
|
|
248
|
+
|
|
249
|
+
const filePreview = wrapper.find('.q-chatbot__file-preview-container')
|
|
250
|
+
expect(filePreview.exists()).toBe(true)
|
|
251
|
+
|
|
252
|
+
const fileName = wrapper.find('.q-chatbot__file-name')
|
|
253
|
+
expect(fileName.exists()).toBe(true)
|
|
254
|
+
expect(fileName.text()).toBe(file.fileData.name)
|
|
255
|
+
|
|
256
|
+
const fileExtension = wrapper.find('.q-chatbot__file-extension')
|
|
257
|
+
expect(fileExtension.exists()).toBe(true)
|
|
258
|
+
expect(fileExtension.text()).toBe('PDF')
|
|
259
|
+
})
|
|
260
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`ChatBotMessage > renders the component with default props 1`] = `
|
|
4
|
+
"<div class="q-chatbot__message-container"><span data-test="" type="img" alt="Sender Image" class="q-chatbot__profile"></span>
|
|
5
|
+
<div class="q-chatbot__message-wrapper">
|
|
6
|
+
<!--v-if-->
|
|
7
|
+
<div class="q-chatbot__message">
|
|
8
|
+
<div class="markdown-renderer q-chatbot__text">
|
|
9
|
+
<p>Hello, this is a test message.</p>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
<!--teleport start-->
|
|
14
|
+
<transition-stub name="fade" appear="true" persisted="false" css="true">
|
|
15
|
+
<!--v-if-->
|
|
16
|
+
</transition-stub>
|
|
17
|
+
<transition-stub name="fade" appear="true" persisted="false" css="true">
|
|
18
|
+
<!--v-if-->
|
|
19
|
+
</transition-stub>
|
|
20
|
+
<!--teleport end-->
|
|
21
|
+
<!--v-if-->
|
|
22
|
+
<div class="q-chatbot__feedback-buttons">
|
|
23
|
+
<div class="q-button-group" role="group"><button type="button" class="q-button q-button--outlined q-button--primary q-button--borderless q-chatbot__good-response-button" title="Good response">
|
|
24
|
+
<!--v-if--><span class="q-button__content"><span data-test="thumb-up"></span> </span>
|
|
25
|
+
</button><button type="button" class="q-button q-button--outlined q-button--primary q-button--borderless q-chatbot__bad-response-button" title="Bad response">
|
|
26
|
+
<!--v-if--><span class="q-button__content"><span data-test="thumb-down"></span> </span>
|
|
27
|
+
</button><button type="button" class="q-button q-button--outlined q-button--primary q-button--borderless q-chatbot__copy-button" title="Copy response">
|
|
28
|
+
<!--v-if--><span class="q-button__content"><span data-test="copy-content"></span> </span>
|
|
29
|
+
</button>
|
|
30
|
+
<!--v-if-->
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="q-chatbot__sender">11:47</div>
|
|
34
|
+
</div>"
|
|
35
|
+
`;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ChatBotMessageSender, FieldData } from '@/components/ChatBot/types'
|
|
1
|
+
import type { ChatBotMessageSender, FieldData } from '@/components/ChatBot/types'
|
|
2
|
+
import type { ChatBotFile } from '../ChatBotInput'
|
|
2
3
|
|
|
3
4
|
export type ChatBotMessageProps = {
|
|
4
5
|
/*
|
|
@@ -27,9 +28,9 @@ export type ChatBotMessageProps = {
|
|
|
27
28
|
dateFormat: string
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
|
-
*
|
|
31
|
+
* File Preview
|
|
31
32
|
*/
|
|
32
|
-
|
|
33
|
+
file?: ChatBotFile
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
36
|
* Default api endpoint
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
exports[`FieldPreview > renders correctly with default props 1`] = `
|
|
4
4
|
"<div class="q-field-preview">
|
|
5
|
-
<div class="q-field-preview__toolbar"><span>Suggestions for field <b>Test Field</b></span></div>
|
|
5
|
+
<div class="q-field-preview__toolbar"><span>Suggestions for field: <b>Test Field</b></span></div>
|
|
6
6
|
<div class="q-field-preview__content">
|
|
7
7
|
<div class="markdown-renderer">
|
|
8
8
|
<p>Hello, World!</p>
|
|
@@ -3,6 +3,7 @@ import { v4 as uuidv4 } from 'uuid'
|
|
|
3
3
|
|
|
4
4
|
import type { Ref } from 'vue'
|
|
5
5
|
import type { ChatMessage, ChatBotMessageSender } from '@/components/ChatBot/types'
|
|
6
|
+
import type { ChatBotFile } from '@/components/ChatBotInput'
|
|
6
7
|
|
|
7
8
|
const messages: Ref<ChatMessage[]> = ref([])
|
|
8
9
|
const nextMessageId = ref(1)
|
|
@@ -11,7 +12,7 @@ export function useChatMessages() {
|
|
|
11
12
|
function addChatMessage(
|
|
12
13
|
message: string,
|
|
13
14
|
sender?: ChatBotMessageSender,
|
|
14
|
-
|
|
15
|
+
file?: ChatBotFile,
|
|
15
16
|
sessionID?: string,
|
|
16
17
|
isWelcomeMessage?: boolean
|
|
17
18
|
) {
|
|
@@ -21,7 +22,7 @@ export function useChatMessages() {
|
|
|
21
22
|
date: new Date(),
|
|
22
23
|
sender: sender || 'bot',
|
|
23
24
|
sessionID: sessionID || uuidv4(),
|
|
24
|
-
|
|
25
|
+
file: file,
|
|
25
26
|
isWelcomeMessage,
|
|
26
27
|
fields: []
|
|
27
28
|
}
|
|
@@ -30,6 +30,7 @@ export function useTexts() {
|
|
|
30
30
|
regenerateResponsePrompt: 'Regenerate a new response for field {0}',
|
|
31
31
|
regenerateResponse: 'Regenerate response',
|
|
32
32
|
generatingResponse: 'Generating',
|
|
33
|
-
suggestionsForField: 'Suggestions for field'
|
|
33
|
+
suggestionsForField: 'Suggestions for field:',
|
|
34
|
+
fileUpload: 'Upload File'
|
|
34
35
|
}
|
|
35
36
|
}
|