@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.
- package/README.md +1 -2
- package/dist/components/ChatBot/types.d.ts +3 -1
- package/dist/components/ChatBotInput/ChatBotInput.vue.d.ts +3 -3
- package/dist/components/ChatBotInput/__tests__/ChatBotInput.spec.d.ts +1 -0
- 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/__tests__/ChatBotMessageButtons.spec.d.ts +1 -0
- package/dist/components/ChatBotMessage/types.d.ts +3 -2
- package/dist/components/ChatToolBar/__tests__/ChatToolBar.spec.d.ts +1 -0
- package/dist/components/FieldPreview/__tests__/FieldPreview.spec.d.ts +1 -0
- package/dist/components/MarkdownRender/__tests__/MarkdownRender.spec.d.ts +1 -0
- package/dist/components/PulseDots/__tests__/PulseDots.spec.d.ts +1 -0
- package/dist/composables/__tests__/useChatMessages.spec.d.ts +1 -0
- package/dist/composables/__tests__/useSSE.spec.d.ts +1 -0
- package/dist/composables/useChatMessages.d.ts +2 -1
- package/dist/composables/useSSE.d.ts +1 -2
- package/dist/composables/useTexts.d.ts +2 -0
- package/dist/index.js +16 -16
- package/dist/index.mjs +2924 -1770
- package/dist/style.css +1 -1
- package/dist/test/setup.d.ts +1 -0
- package/dist/utils/__tests__/parseFieldValue.spec.d.ts +1 -0
- package/package.json +27 -5
- package/src/assets/styles/preview-file.scss +70 -0
- package/src/assets/styles/styles.scss +190 -222
- package/src/components/ChatBot/ChatBot.vue +345 -368
- package/src/components/ChatBot/types.ts +35 -33
- package/src/components/ChatBotInput/ChatBotInput.vue +188 -190
- package/src/components/ChatBotInput/__tests__/ChatBotInput.spec.ts +279 -0
- package/src/components/ChatBotInput/__tests__/__snapshots__/ChatBotInput.spec.ts.snap +25 -0
- package/src/components/ChatBotInput/index.ts +2 -2
- package/src/components/ChatBotInput/types.ts +25 -25
- package/src/components/ChatBotMessage/ChatBotMessage.vue +159 -134
- package/src/components/ChatBotMessage/ChatBotMessageButtons.vue +179 -164
- package/src/components/ChatBotMessage/__tests__/ChatBotMessage.spec.ts +256 -0
- package/src/components/ChatBotMessage/__tests__/ChatBotMessageButtons.spec.ts +199 -0
- package/src/components/ChatBotMessage/__tests__/__snapshots__/ChatBotMessage.spec.ts.snap +35 -0
- package/src/components/ChatBotMessage/__tests__/__snapshots__/ChatBotMessageButtons.spec.ts.snap +25 -0
- package/src/components/ChatBotMessage/types.ts +54 -53
- package/src/components/ChatToolBar/ChatToolBar.vue +68 -64
- package/src/components/ChatToolBar/__tests__/ChatToolBar.spec.ts +118 -0
- package/src/components/ChatToolBar/__tests__/__snapshots__/ChatToolBar.spec.ts.snap +11 -0
- package/src/components/ChatToolBar/types.ts +12 -12
- package/src/components/FieldPreview/FieldPreview.vue +56 -58
- package/src/components/FieldPreview/__tests__/FieldPreview.spec.ts +72 -0
- package/src/components/FieldPreview/__tests__/__snapshots__/FieldPreview.spec.ts.snap +25 -0
- package/src/components/FieldPreview/field-preview.scss +26 -26
- package/src/components/FieldPreview/types.ts +5 -5
- package/src/components/MarkdownRender/MarkdownRender.vue +15 -15
- package/src/components/MarkdownRender/__tests__/MarkdownRender.spec.ts +68 -0
- package/src/components/MarkdownRender/__tests__/__snapshots__/MarkdownRender.spec.ts.snap +8 -0
- package/src/components/MarkdownRender/markdown-render.scss +19 -20
- package/src/components/MarkdownRender/types.ts +3 -3
- package/src/components/PulseDots/PulseDots.vue +17 -17
- package/src/components/PulseDots/__tests__/PulseDots.spec.ts +35 -0
- package/src/components/PulseDots/__tests__/__snapshots__/PulseDots.spec.ts.snap +7 -0
- package/src/components/PulseDots/__tests__/__snapshots__/pulse-dots.spec.ts.snap +7 -0
- package/src/components/PulseDots/pulse-dots.scss +24 -23
- package/src/composables/__tests__/useChatMessages.spec.ts +51 -0
- package/src/composables/__tests__/useSSE.spec.ts +132 -0
- package/src/composables/useChatApi.ts +128 -134
- package/src/composables/useChatMessages.ts +46 -48
- package/src/composables/useSSE.ts +75 -76
- package/src/composables/useTexts.ts +30 -30
- package/src/test/setup.ts +36 -0
- package/src/utils/__tests__/parseFieldValue.spec.ts +27 -0
- package/src/utils/parseFieldValue.ts +12 -0
- package/src/utils/helper.ts +0 -12
- /package/dist/utils/{helper.d.ts → parseFieldValue.d.ts} +0 -0
|
@@ -1,169 +1,184 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
+
})
|