@quidgest/chatbot 0.5.1 → 0.5.3

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 (70) hide show
  1. package/README.md +1 -2
  2. package/dist/components/ChatBot/types.d.ts +3 -1
  3. package/dist/components/ChatBotInput/ChatBotInput.vue.d.ts +3 -3
  4. package/dist/components/ChatBotInput/__tests__/ChatBotInput.spec.d.ts +1 -0
  5. package/dist/components/ChatBotInput/index.d.ts +2 -2
  6. package/dist/components/ChatBotInput/types.d.ts +4 -4
  7. package/dist/components/ChatBotMessage/__tests__/ChatBotMessage.spec.d.ts +1 -0
  8. package/dist/components/ChatBotMessage/__tests__/ChatBotMessageButtons.spec.d.ts +1 -0
  9. package/dist/components/ChatBotMessage/types.d.ts +3 -2
  10. package/dist/components/ChatToolBar/__tests__/ChatToolBar.spec.d.ts +1 -0
  11. package/dist/components/FieldPreview/__tests__/FieldPreview.spec.d.ts +1 -0
  12. package/dist/components/MarkdownRender/__tests__/MarkdownRender.spec.d.ts +1 -0
  13. package/dist/components/PulseDots/__tests__/PulseDots.spec.d.ts +1 -0
  14. package/dist/composables/__tests__/useChatMessages.spec.d.ts +1 -0
  15. package/dist/composables/__tests__/useSSE.spec.d.ts +1 -0
  16. package/dist/composables/useChatMessages.d.ts +2 -1
  17. package/dist/composables/useSSE.d.ts +1 -2
  18. package/dist/composables/useTexts.d.ts +2 -0
  19. package/dist/index.js +16 -16
  20. package/dist/index.mjs +2924 -1770
  21. package/dist/style.css +1 -1
  22. package/dist/test/setup.d.ts +1 -0
  23. package/dist/utils/__tests__/parseFieldValue.spec.d.ts +1 -0
  24. package/package.json +27 -5
  25. package/src/assets/styles/preview-file.scss +70 -0
  26. package/src/assets/styles/styles.scss +190 -222
  27. package/src/components/ChatBot/ChatBot.vue +345 -368
  28. package/src/components/ChatBot/types.ts +35 -33
  29. package/src/components/ChatBotInput/ChatBotInput.vue +188 -190
  30. package/src/components/ChatBotInput/__tests__/ChatBotInput.spec.ts +279 -0
  31. package/src/components/ChatBotInput/__tests__/__snapshots__/ChatBotInput.spec.ts.snap +25 -0
  32. package/src/components/ChatBotInput/index.ts +2 -2
  33. package/src/components/ChatBotInput/types.ts +25 -25
  34. package/src/components/ChatBotMessage/ChatBotMessage.vue +159 -134
  35. package/src/components/ChatBotMessage/ChatBotMessageButtons.vue +179 -164
  36. package/src/components/ChatBotMessage/__tests__/ChatBotMessage.spec.ts +256 -0
  37. package/src/components/ChatBotMessage/__tests__/ChatBotMessageButtons.spec.ts +199 -0
  38. package/src/components/ChatBotMessage/__tests__/__snapshots__/ChatBotMessage.spec.ts.snap +35 -0
  39. package/src/components/ChatBotMessage/__tests__/__snapshots__/ChatBotMessageButtons.spec.ts.snap +25 -0
  40. package/src/components/ChatBotMessage/types.ts +54 -53
  41. package/src/components/ChatToolBar/ChatToolBar.vue +68 -64
  42. package/src/components/ChatToolBar/__tests__/ChatToolBar.spec.ts +118 -0
  43. package/src/components/ChatToolBar/__tests__/__snapshots__/ChatToolBar.spec.ts.snap +11 -0
  44. package/src/components/ChatToolBar/types.ts +12 -12
  45. package/src/components/FieldPreview/FieldPreview.vue +56 -58
  46. package/src/components/FieldPreview/__tests__/FieldPreview.spec.ts +72 -0
  47. package/src/components/FieldPreview/__tests__/__snapshots__/FieldPreview.spec.ts.snap +25 -0
  48. package/src/components/FieldPreview/field-preview.scss +26 -26
  49. package/src/components/FieldPreview/types.ts +5 -5
  50. package/src/components/MarkdownRender/MarkdownRender.vue +15 -15
  51. package/src/components/MarkdownRender/__tests__/MarkdownRender.spec.ts +68 -0
  52. package/src/components/MarkdownRender/__tests__/__snapshots__/MarkdownRender.spec.ts.snap +8 -0
  53. package/src/components/MarkdownRender/markdown-render.scss +19 -20
  54. package/src/components/MarkdownRender/types.ts +3 -3
  55. package/src/components/PulseDots/PulseDots.vue +17 -17
  56. package/src/components/PulseDots/__tests__/PulseDots.spec.ts +35 -0
  57. package/src/components/PulseDots/__tests__/__snapshots__/PulseDots.spec.ts.snap +7 -0
  58. package/src/components/PulseDots/__tests__/__snapshots__/pulse-dots.spec.ts.snap +7 -0
  59. package/src/components/PulseDots/pulse-dots.scss +24 -23
  60. package/src/composables/__tests__/useChatMessages.spec.ts +51 -0
  61. package/src/composables/__tests__/useSSE.spec.ts +132 -0
  62. package/src/composables/useChatApi.ts +128 -134
  63. package/src/composables/useChatMessages.ts +46 -48
  64. package/src/composables/useSSE.ts +75 -76
  65. package/src/composables/useTexts.ts +30 -30
  66. package/src/test/setup.ts +36 -0
  67. package/src/utils/__tests__/parseFieldValue.spec.ts +27 -0
  68. package/src/utils/parseFieldValue.ts +12 -0
  69. package/src/utils/helper.ts +0 -12
  70. /package/dist/utils/{helper.d.ts → parseFieldValue.d.ts} +0 -0
@@ -1,169 +1,184 @@
1
1
  <template>
2
- <q-dialog
3
- v-model="showDialog"
4
- :buttons="commentButtons">
5
- <template #body>
6
- <div class="q-chatbot__dialog-title">
7
- {{ texts.commentDialogTitle }}
8
- </div>
9
- <q-text-field
10
- v-model="feedbackComment"
11
- :maxLength="150"
12
- size="large"
13
- :placeholder="texts.commentPlaceholder" />
14
- </template>
15
- </q-dialog>
16
-
17
- <div
18
- v-if="showButtons"
19
- class="q-chatbot__feedback-buttons">
20
- <q-button-group>
21
- <q-button
22
- :title="texts.goodResponse"
23
- borderless
24
- :disabled="props.loading"
25
- @click="openFeedbackDialog(1)">
26
- <q-icon icon="thumb-up" />
27
- </q-button>
28
- <q-button
29
- :title="texts.badResponse"
30
- borderless
31
- :disabled="props.loading"
32
- @click="openFeedbackDialog(0)">
33
- <q-icon icon="thumb-down" />
34
- </q-button>
35
- <q-button
36
- :title="texts.copyResponse"
37
- borderless
38
- :disabled="props.loading"
39
- @click="copyResponse">
40
- <q-icon icon="copy-content" />
41
- </q-button>
42
- <q-button
43
- v-if="showApplyAll"
44
- :title="texts.applyAll"
45
- borderless
46
- :disabled="blockApplyAllButton"
47
- :readonly="blockApplyAllButton"
48
- @click="onApplyAll">
49
- <q-icon icon="apply-all" />
50
- </q-button>
51
- </q-button-group>
52
- </div>
53
- <div class="q-chatbot__sender">
54
- {{ messageDate }}
55
- </div>
2
+ <q-dialog
3
+ v-model="showDialog"
4
+ inline
5
+ :buttons="commentButtons">
6
+ <template #body>
7
+ <div class="q-chatbot__dialog-title">
8
+ {{ texts.commentDialogTitle }}
9
+ </div>
10
+ <q-text-field
11
+ v-model="feedbackComment"
12
+ class="q-chatbot__dialog-comment-input"
13
+ :max-length="150"
14
+ size="large"
15
+ :placeholder="texts.commentPlaceholder" />
16
+ </template>
17
+ </q-dialog>
18
+
19
+ <div
20
+ v-if="showButtons"
21
+ class="q-chatbot__feedback-buttons">
22
+ <q-button-group>
23
+ <q-button
24
+ :title="texts.goodResponse"
25
+ class="q-chatbot__good-response-button"
26
+ borderless
27
+ :disabled="props.loading"
28
+ @click="handleGoodResponseClick">
29
+ <q-icon icon="thumb-up" />
30
+ </q-button>
31
+ <q-button
32
+ :title="texts.badResponse"
33
+ class="q-chatbot__bad-response-button"
34
+ borderless
35
+ :disabled="props.loading"
36
+ @click="handleBadResponseClick">
37
+ <q-icon icon="thumb-down" />
38
+ </q-button>
39
+ <q-button
40
+ :title="texts.copyResponse"
41
+ class="q-chatbot__copy-button"
42
+ borderless
43
+ :disabled="props.loading"
44
+ @click="copyResponse">
45
+ <q-icon icon="copy-content" />
46
+ </q-button>
47
+ <q-button
48
+ v-if="showApplyAll"
49
+ :title="texts.applyAll"
50
+ class="q-chatbot__apply-all-button"
51
+ borderless
52
+ :disabled="blockApplyAllButton"
53
+ :readonly="blockApplyAllButton"
54
+ @click="onApplyAll">
55
+ <q-icon icon="apply-all" />
56
+ </q-button>
57
+ </q-button-group>
58
+ </div>
59
+ <div class="q-chatbot__sender">
60
+ {{ messageDate }}
61
+ </div>
56
62
  </template>
57
63
 
58
64
  <script setup lang="ts">
59
- import { computed, ref } from 'vue'
60
- import { QDialog } from '@quidgest/ui/components'
61
- import type { ChatBotMessageButtonsProps } from './'
62
-
63
- // Composables
64
- import { useTexts } from '@/composables/useTexts'
65
- import { useChatMessages } from '@/composables/useChatMessages'
66
-
67
- const texts = useTexts()
68
- const { getLastMessage } = useChatMessages()
69
-
70
- const props = defineProps<ChatBotMessageButtonsProps>()
71
- const showDialog = ref(false)
72
- const feedbackComment = ref('')
73
- const currentFeedback = ref<number | null>(null)
74
- const blockApplyAll = ref(false)
75
- const blockApplyAllButton = computed(() => {
76
- return props.loading || blockApplyAll.value
77
- })
78
-
79
- const date = props.date || new Date()
80
-
81
- const emits = defineEmits<{
82
- (e: 'submit-feedback', feedback: number, comment: string): void
83
- (e: 'copy-response'): void
84
- (e: 'apply-all'): void
85
- }>()
86
-
87
- const lastMessage = getLastMessage()
88
-
89
- const showApplyAll = computed(() => {
90
- if (!lastMessage) return false
91
-
92
- return lastMessage.fields && lastMessage.fields.length > 1
93
- })
94
-
95
- const commentButtons = [
96
- {
97
- id: 'confirm-btn',
98
- action: submitFeedback,
99
- props: {
100
- label: texts.submitButton
101
- },
102
- icon: {
103
- icon: 'submit'
104
- }
105
- },
106
- {
107
- id: 'cancel-btn',
108
- props: {
109
- label: texts.cancelButton
110
- },
111
- icon: {
112
- icon: 'cancel'
113
- }
114
- }
115
- ]
116
-
117
- const getLocaleDate = computed(() => {
118
- if (!props.dateFormat) return date.toLocaleString()
119
-
120
- return formatDate(date, 'HH:mm')
121
- })
122
-
123
- const messageDate = computed(() => {
124
- return `${getLocaleDate.value}`
125
- })
126
-
127
- function formatDate(date: Date, format: string) {
128
- const day = date.getDate().toString().padStart(2, '0')
129
- const month = (date.getMonth() + 1).toString().padStart(2, '0')
130
- const year = date.getFullYear().toString().padStart(2, '0')
131
- const hours = date.getHours().toString().padStart(2, '0')
132
- const minutes = date.getMinutes().toString().padStart(2, '0')
133
- const seconds = date.getSeconds().toString().padStart(2, '0')
134
-
135
- return format
136
- .replace('dd', day)
137
- .replace('MM', month)
138
- .replace('yyyy', year)
139
- .replace('HH', hours)
140
- .replace('mm', minutes)
141
- .replace('ss', seconds)
142
- }
143
-
144
- function openFeedbackDialog(feedback: number) {
145
- showDialog.value = true
146
- feedbackComment.value = ''
147
- currentFeedback.value = feedback
148
- }
149
-
150
- function onApplyAll() {
151
- if (blockApplyAll.value) return
152
- blockApplyAll.value = true
153
-
154
- emits('apply-all')
155
- }
156
-
157
- function submitFeedback() {
158
- if (currentFeedback.value === null) return
159
-
160
- emits('submit-feedback', currentFeedback.value, feedbackComment.value)
161
- showDialog.value = false
162
- feedbackComment.value = ''
163
- currentFeedback.value = null
164
- }
165
-
166
- function copyResponse() {
167
- emits('copy-response')
168
- }
65
+ import { computed, ref } from 'vue'
66
+ import { QDialog, QButtonGroup, QButton, QTextField, QIcon } from '@quidgest/ui/components'
67
+ import type { ChatBotMessageButtonsProps } from './'
68
+
69
+ // Composables
70
+ import { useTexts } from '@/composables/useTexts'
71
+ import { useChatMessages } from '@/composables/useChatMessages'
72
+
73
+ const props = defineProps<ChatBotMessageButtonsProps>()
74
+ const emit = defineEmits<{
75
+ (e: 'submit-feedback', feedback: number, comment: string): void
76
+ (e: 'copy-response'): void
77
+ (e: 'apply-all'): void
78
+ }>()
79
+ const texts = useTexts()
80
+ const { getLastMessage } = useChatMessages()
81
+
82
+ const showDialog = ref(false)
83
+ const feedbackComment = ref('')
84
+ const currentFeedback = ref<number | null>(null)
85
+ const blockApplyAll = ref(false)
86
+ const blockApplyAllButton = computed(() => {
87
+ return props.loading || blockApplyAll.value
88
+ })
89
+
90
+ const date = props.date || new Date()
91
+
92
+ const lastMessage = getLastMessage()
93
+
94
+ const showApplyAll = computed(() => {
95
+ if (!lastMessage) return false
96
+
97
+ return lastMessage.fields && lastMessage.fields.length > 1
98
+ })
99
+
100
+ const commentButtons = [
101
+ {
102
+ id: 'confirm-btn',
103
+ action: submitFeedback,
104
+ props: {
105
+ label: texts.submitButton,
106
+ class: 'q-chatbot__dialog-confirm-button'
107
+ },
108
+ icon: {
109
+ icon: 'submit'
110
+ }
111
+ },
112
+ {
113
+ id: 'cancel-btn',
114
+ props: {
115
+ label: texts.cancelButton,
116
+ class: 'q-chatbot__dialog-cancel-button'
117
+ },
118
+ icon: {
119
+ icon: 'cancel'
120
+ }
121
+ }
122
+ ]
123
+
124
+ const getLocaleDate = computed(() => {
125
+ if (!props.dateFormat) return date.toLocaleString()
126
+
127
+ return formatDate(date, 'HH:mm')
128
+ })
129
+
130
+ const messageDate = computed(() => {
131
+ return `${getLocaleDate.value}`
132
+ })
133
+
134
+ function formatDate(date: Date, format: string) {
135
+ const day = date.getDate().toString().padStart(2, '0')
136
+ const month = (date.getMonth() + 1).toString().padStart(2, '0')
137
+ const year = date.getFullYear().toString().padStart(2, '0')
138
+ const hours = date.getHours().toString().padStart(2, '0')
139
+ const minutes = date.getMinutes().toString().padStart(2, '0')
140
+ const seconds = date.getSeconds().toString().padStart(2, '0')
141
+
142
+ return format
143
+ .replace('dd', day)
144
+ .replace('MM', month)
145
+ .replace('yyyy', year)
146
+ .replace('HH', hours)
147
+ .replace('mm', minutes)
148
+ .replace('ss', seconds)
149
+ }
150
+
151
+ function openFeedbackDialog(feedback: number) {
152
+ showDialog.value = true
153
+ feedbackComment.value = ''
154
+ currentFeedback.value = feedback
155
+ }
156
+
157
+ function handleBadResponseClick() {
158
+ openFeedbackDialog(0)
159
+ }
160
+
161
+ function handleGoodResponseClick() {
162
+ openFeedbackDialog(1)
163
+ }
164
+
165
+ function onApplyAll() {
166
+ if (blockApplyAll.value) return
167
+ blockApplyAll.value = true
168
+
169
+ emit('apply-all')
170
+ }
171
+
172
+ function submitFeedback() {
173
+ if (!currentFeedback.value) return
174
+
175
+ emit('submit-feedback', currentFeedback.value, feedbackComment.value)
176
+ showDialog.value = false
177
+ feedbackComment.value = ''
178
+ currentFeedback.value = null
179
+ }
180
+
181
+ function copyResponse() {
182
+ emit('copy-response')
183
+ }
169
184
  </script>
@@ -0,0 +1,256 @@
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
+ type: 'text',
85
+ text: 'Sample Field',
86
+ name: 'This is a sample field value'
87
+ }
88
+ ]
89
+ }
90
+ })
91
+
92
+ const fieldPreview = wrapper.findComponent(FieldPreview)
93
+ expect(fieldPreview.exists()).toBe(true)
94
+ })
95
+
96
+ it('renders a normal when no fields are provided and the sender is user', () => {
97
+ const wrapper = mount(ChatBotMessage, {
98
+ props: { ...props, sender: 'user' }
99
+ })
100
+
101
+ const fieldPreview = wrapper.findComponent(FieldPreview)
102
+ expect(fieldPreview.exists()).toBe(false)
103
+
104
+ const markdownRender = wrapper.findComponent(MarkdownRender)
105
+ expect(markdownRender.exists()).toBe(false)
106
+
107
+ const messageDiv = wrapper.find('.q-chatbot__user-text')
108
+ expect(messageDiv.exists()).toBe(true)
109
+ expect(messageDiv.text()).toBe(props.message)
110
+ })
111
+
112
+ it('renders markdown-render when no fields are provided', () => {
113
+ const wrapper = mount(ChatBotMessage, { props })
114
+
115
+ const markdownRender = wrapper.findComponent(MarkdownRender)
116
+ expect(markdownRender.exists()).toBe(true)
117
+ })
118
+
119
+ it('adds the text to the clipboard when copy event is emmited', async () => {
120
+ const wrapper = mount(ChatBotMessage, { props })
121
+
122
+ const messageButtons = wrapper.findComponent(ChatBotMessageButtons)
123
+ const copyButton = messageButtons.find('.q-chatbot__copy-button')
124
+
125
+ await copyButton.trigger('click')
126
+ const clipboardText = await navigator.clipboard.readText()
127
+
128
+ expect(navigator.clipboard).toBeDefined()
129
+ expect(clipboardText).toBe(props.message)
130
+ })
131
+
132
+ it('does not add the text to the clipboard if message is empty', async () => {
133
+ const wrapper = mount(ChatBotMessage, { props: { ...props, message: '' } })
134
+ const messageButtons = wrapper.findComponent(ChatBotMessageButtons)
135
+ const copyButton = messageButtons.find('.q-chatbot__copy-button')
136
+
137
+ await copyButton.trigger('click')
138
+ const clipboardText = await navigator.clipboard.readText()
139
+
140
+ expect(navigator.clipboard).toBeDefined()
141
+ expect(clipboardText).toBe('')
142
+ })
143
+
144
+ it('catchs errors when trying to copy to clipboard', async () => {
145
+ const wrapper = mount(ChatBotMessage, { props })
146
+ const messageButtons = wrapper.findComponent(ChatBotMessageButtons)
147
+ const copyButton = messageButtons.find('.q-chatbot__copy-button')
148
+
149
+ vi.spyOn(navigator.clipboard, 'writeText').mockRejectedValueOnce(
150
+ new Error('Clipboard error')
151
+ )
152
+ // Suppress console.error for this test
153
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
154
+
155
+ await copyButton.trigger('click')
156
+ const clipboardText = await navigator.clipboard.readText()
157
+
158
+ expect(navigator.clipboard).toBeDefined()
159
+ expect(clipboardText).toBe('')
160
+ consoleErrorSpy.mockRestore()
161
+ })
162
+
163
+ it('emits apply-fields event with fields when apply all button is clicked', async () => {
164
+ const testFields = [
165
+ {
166
+ type: 'text',
167
+ text: 'Sample Field',
168
+ name: 'This is a sample field value'
169
+ },
170
+ {
171
+ type: 'number',
172
+ text: 'Number Field',
173
+ name: '42'
174
+ }
175
+ ]
176
+ addChatMessage('Test message 1', 'bot').fields = testFields
177
+
178
+ const wrapper = mount(ChatBotMessage, {
179
+ props: { ...props, sender: 'bot', fields: testFields }
180
+ })
181
+
182
+ const messageButtons = wrapper.findComponent(ChatBotMessageButtons)
183
+ const applyAllButton = messageButtons.find('.q-chatbot__apply-all-button')
184
+
185
+ expect(applyAllButton.exists()).toBe(true)
186
+ await applyAllButton.trigger('click')
187
+
188
+ const fieldsToApply = testFields.map((field) => {
189
+ return {
190
+ name: field.name,
191
+ text: parseFieldValue(field.type, field.text)
192
+ }
193
+ })
194
+
195
+ expect(wrapper.emitted('apply-fields')).toBeTruthy()
196
+ expect(wrapper.emitted('apply-fields')?.[0]).toEqual([fieldsToApply])
197
+ })
198
+
199
+ it('emits apply-fieldd when a single field is applied', async () => {
200
+ const testFields = [
201
+ {
202
+ type: 'text',
203
+ text: 'Sample Field',
204
+ name: 'This is a sample field value'
205
+ }
206
+ ]
207
+
208
+ addChatMessage('Test message 1', 'bot').fields = testFields
209
+
210
+ const wrapper = mount(ChatBotMessage, {
211
+ props: { ...props, sender: 'bot', fields: testFields }
212
+ })
213
+ const fieldPreview = wrapper.findComponent(FieldPreview)
214
+ expect(fieldPreview.exists()).toBe(true)
215
+
216
+ const applyButton = fieldPreview.find("[data-testid='apply-button']")
217
+ expect(applyButton.exists()).toBe(true)
218
+
219
+ await applyButton.trigger('click')
220
+
221
+ const fieldToApply = [
222
+ {
223
+ name: testFields[0].name,
224
+ text: parseFieldValue(testFields[0].type, testFields[0].text)
225
+ }
226
+ ]
227
+
228
+ expect(wrapper.emitted('apply-fields')).toBeTruthy()
229
+ expect(wrapper.emitted('apply-fields')?.[0]).toEqual([fieldToApply])
230
+ })
231
+
232
+ it('renders a different preview when the file is not an image', async () => {
233
+ const file: ChatBotFile = {
234
+ fileData: new File(['dummy content'], 'example.pdf', { type: 'application/pdf' }),
235
+ previewUrl: ''
236
+ }
237
+
238
+ const wrapper = mount(ChatBotMessage, {
239
+ props: { ...props, file }
240
+ })
241
+
242
+ const img = wrapper.find('.q-chatbot__image-preview img')
243
+ expect(img.exists()).toBe(false)
244
+
245
+ const filePreview = wrapper.find('.q-chatbot__file-preview-container')
246
+ expect(filePreview.exists()).toBe(true)
247
+
248
+ const fileName = wrapper.find('.q-chatbot__file-name')
249
+ expect(fileName.exists()).toBe(true)
250
+ expect(fileName.text()).toBe(file.fileData.name)
251
+
252
+ const fileExtension = wrapper.find('.q-chatbot__file-extension')
253
+ expect(fileExtension.exists()).toBe(true)
254
+ expect(fileExtension.text()).toBe('PDF')
255
+ })
256
+ })