@quidgest/chatbot 0.5.0 → 0.5.3-dev.0
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/ChatBot.vue.d.ts +2 -0
- package/dist/components/ChatBot/types.d.ts +2 -1
- package/dist/components/ChatBotInput/ChatBotInput.vue.d.ts +2 -2
- package/dist/components/ChatBotInput/__tests__/ChatBotInput.spec.d.ts +1 -0
- package/dist/components/ChatBotMessage/ChatBotMessage.vue.d.ts +3 -1
- package/dist/components/ChatBotMessage/__tests__/ChatBotMessageButtons.spec.d.ts +1 -0
- package/dist/components/ChatToolBar/__tests__/ChatToolBar.spec.d.ts +1 -0
- package/dist/components/FieldPreview/FieldPreview.vue.d.ts +2 -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/useChatApi.d.ts +1 -1
- package/dist/composables/useChatMessages.d.ts +2 -1
- package/dist/composables/useSSE.d.ts +1 -2
- package/dist/composables/useTexts.d.ts +5 -0
- package/dist/index.js +25 -25
- package/dist/index.mjs +2317 -1810
- 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 +28 -7
- package/src/assets/styles/styles.scss +212 -220
- package/src/components/ChatBot/ChatBot.vue +346 -370
- package/src/components/ChatBot/types.ts +34 -33
- package/src/components/ChatBotInput/ChatBotInput.vue +181 -190
- package/src/components/ChatBotInput/__tests__/ChatBotInput.spec.ts +292 -0
- package/src/components/ChatBotInput/__tests__/__snapshots__/ChatBotInput.spec.ts.snap +25 -0
- package/src/components/ChatBotInput/types.ts +24 -24
- package/src/components/ChatBotMessage/ChatBotMessage.vue +133 -134
- package/src/components/ChatBotMessage/ChatBotMessageButtons.vue +179 -164
- package/src/components/ChatBotMessage/__tests__/ChatBotMessageButtons.spec.ts +199 -0
- package/src/components/ChatBotMessage/__tests__/__snapshots__/ChatBotMessageButtons.spec.ts.snap +25 -0
- package/src/components/ChatBotMessage/types.ts +52 -52
- package/src/components/ChatToolBar/ChatToolBar.vue +69 -64
- package/src/components/ChatToolBar/__tests__/ChatToolBar.spec.ts +138 -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 +83 -63
- package/src/components/FieldPreview/__tests__/FieldPreview.spec.ts +72 -0
- package/src/components/FieldPreview/__tests__/__snapshots__/FieldPreview.spec.ts.snap +19 -0
- package/src/components/FieldPreview/field-preview.scss +28 -24
- package/src/components/FieldPreview/types.ts +5 -5
- package/src/components/MarkdownRender/MarkdownRender.vue +16 -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 +64 -0
- package/src/composables/__tests__/useSSE.spec.ts +132 -0
- package/src/composables/useChatApi.ts +132 -134
- package/src/composables/useChatMessages.ts +50 -48
- package/src/composables/useSSE.ts +75 -73
- package/src/composables/useTexts.ts +33 -30
- package/src/test/setup.ts +41 -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
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Componets
|
|
2
|
+
import { FieldPreview } from '..'
|
|
3
|
+
|
|
4
|
+
// Utils
|
|
5
|
+
import { mount } from '@vue/test-utils'
|
|
6
|
+
import { describe, it, expect } from 'vitest'
|
|
7
|
+
|
|
8
|
+
// Types
|
|
9
|
+
import type { FieldPreviewProps } from '..'
|
|
10
|
+
|
|
11
|
+
describe('FieldPreview', () => {
|
|
12
|
+
const props: FieldPreviewProps = {
|
|
13
|
+
name: 'Test Field',
|
|
14
|
+
text: 'Hello, World!',
|
|
15
|
+
type: 'text',
|
|
16
|
+
disabled: false,
|
|
17
|
+
applied: false
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
it('renders correctly with default props', () => {
|
|
21
|
+
const wrapper = mount(FieldPreview, {
|
|
22
|
+
props: { ...props }
|
|
23
|
+
})
|
|
24
|
+
expect(wrapper.html()).toMatchSnapshot()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('renders the correct componentd for text fields', async () => {
|
|
28
|
+
const wrapper = mount(FieldPreview, {
|
|
29
|
+
props: { ...props }
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
expect(wrapper.find('.markdown-renderer').exists()).toBe(true)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('renders the correct componentd for multiline_text fields', async () => {
|
|
36
|
+
const wrapper = mount(FieldPreview, {
|
|
37
|
+
props: { ...props, type: 'multiline_text' }
|
|
38
|
+
})
|
|
39
|
+
expect(wrapper.find('.markdown-renderer').exists()).toBe(true)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('renders the correct default component for unknown field types', async () => {
|
|
43
|
+
const wrapper = mount(FieldPreview, {
|
|
44
|
+
props: { ...props, type: 'boolean' }
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const textElement = wrapper.find('.q-field-preview__content div')
|
|
48
|
+
expect(textElement.exists()).toBe(true)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('emits the right event on apply buttons is clicked', async () => {
|
|
52
|
+
const wrapper = mount(FieldPreview, {
|
|
53
|
+
props: { ...props }
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const applyButton = wrapper.find('[data-testid="apply-button"]')
|
|
57
|
+
await applyButton.trigger('click')
|
|
58
|
+
|
|
59
|
+
expect(wrapper.emitted()['apply']).toBeTruthy()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('does not emit the apply event when the button is disabled', async () => {
|
|
63
|
+
const wrapper = mount(FieldPreview, {
|
|
64
|
+
props: { ...props, applied: true }
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const applyButton = wrapper.find('[data-testid="apply-button"]')
|
|
68
|
+
await applyButton.trigger('click')
|
|
69
|
+
|
|
70
|
+
expect(wrapper.emitted()['apply']).toBeFalsy()
|
|
71
|
+
})
|
|
72
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`FieldPreview > renders correctly with default props 1`] = `
|
|
4
|
+
"<div class="q-field-preview">
|
|
5
|
+
<div class="q-field-preview__toolbar"><span>Suggestions for field <b>Test Field</b></span></div>
|
|
6
|
+
<div class="q-field-preview__content">
|
|
7
|
+
<div class="markdown-renderer">
|
|
8
|
+
<p>Hello, World!</p>
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="q-field-preview__footer">
|
|
12
|
+
<div class="q-button-group" role="group"><button type="button" class="q-button q-button--outlined q-button--primary q-button--borderless" title="Regenerate response">
|
|
13
|
+
<!--v-if--><span class="q-button__content"><span data-test="reset"></span> </span>
|
|
14
|
+
</button><button type="button" class="q-button q-button--outlined q-button--primary q-button--borderless" data-testid="apply-button">
|
|
15
|
+
<!--v-if--><span class="q-button__content"><span data-test="apply"></span> Apply</span>
|
|
16
|
+
</button></div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>"
|
|
19
|
+
`;
|
|
@@ -1,34 +1,38 @@
|
|
|
1
1
|
.q-field-preview {
|
|
2
|
-
|
|
2
|
+
position: relative;
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
margin: 1rem 0.25rem;
|
|
6
|
+
|
|
7
|
+
&__toolbar {
|
|
8
|
+
z-index: 1;
|
|
3
9
|
display: flex;
|
|
4
|
-
flex-direction:
|
|
5
|
-
|
|
10
|
+
flex-direction: row;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: space-between;
|
|
13
|
+
padding: 0.1rem 0.2rem;
|
|
14
|
+
}
|
|
6
15
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
16
|
+
&__content {
|
|
17
|
+
position: relative;
|
|
18
|
+
background-color: #f5f5f5;
|
|
19
|
+
border: 1px solid #e0e0e0;
|
|
20
|
+
border-radius: 6px;
|
|
21
|
+
padding: 0.75rem 1rem;
|
|
22
|
+
font-size: 0.875rem;
|
|
15
23
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
background-color: #f5f5f5;
|
|
19
|
-
border: 1px solid #e0e0e0;
|
|
20
|
-
border-radius: 6px;
|
|
21
|
-
padding: 0.75rem 1rem;
|
|
22
|
-
font-size: 0.875rem;
|
|
24
|
+
&.preserve-whitespace {
|
|
25
|
+
white-space: pre-wrap;
|
|
23
26
|
}
|
|
27
|
+
}
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
&__footer {
|
|
30
|
+
display: flex;
|
|
31
|
+
flex-direction: row;
|
|
32
|
+
margin-top: 0.25rem;
|
|
33
|
+
}
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
.q-field-preview:first-child {
|
|
33
|
-
|
|
37
|
+
margin: 0 1rem 0.25rem 0.25rem;
|
|
34
38
|
}
|
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
<div
|
|
3
|
+
class="markdown-renderer"
|
|
4
|
+
v-html="renderedContent"></div>
|
|
5
5
|
</template>
|
|
6
6
|
|
|
7
7
|
<script setup lang="ts">
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
import { ref, computed } from 'vue'
|
|
9
|
+
import type { MarkdownRenderProps } from './types'
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
import MarkdownIt from 'markdown-it'
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
const props = defineProps<MarkdownRenderProps>()
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
const md = ref(
|
|
16
|
+
new MarkdownIt({
|
|
17
|
+
html: true,
|
|
18
|
+
breaks: true,
|
|
19
|
+
...(props.options ?? {})
|
|
20
|
+
})
|
|
21
|
+
)
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
if (props.plugins) props.plugins.forEach((plugin) => md.value.use(plugin))
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
const renderedContent = computed(() => md.value.render(props.source))
|
|
25
26
|
</script>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Components
|
|
2
|
+
import { MarkdownRender } from '..'
|
|
3
|
+
|
|
4
|
+
// Utils
|
|
5
|
+
import { mount } from '@vue/test-utils'
|
|
6
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
7
|
+
|
|
8
|
+
// Types
|
|
9
|
+
import type MarkdownIt from 'markdown-it'
|
|
10
|
+
|
|
11
|
+
// Mock a simple markdown-it plugin
|
|
12
|
+
const mockPlugin = vi.fn((md: MarkdownIt) => {
|
|
13
|
+
md.core.ruler.push('test:rule', (state) => {
|
|
14
|
+
state.tokens.push({
|
|
15
|
+
type: 'test_token',
|
|
16
|
+
content: 'test',
|
|
17
|
+
tag: '',
|
|
18
|
+
attrs: null,
|
|
19
|
+
map: null,
|
|
20
|
+
nesting: 0,
|
|
21
|
+
level: 0,
|
|
22
|
+
children: null,
|
|
23
|
+
markup: '',
|
|
24
|
+
info: '',
|
|
25
|
+
meta: undefined,
|
|
26
|
+
block: false,
|
|
27
|
+
hidden: false,
|
|
28
|
+
attrIndex: function () {
|
|
29
|
+
throw new Error()
|
|
30
|
+
},
|
|
31
|
+
attrPush: function () {
|
|
32
|
+
throw new Error()
|
|
33
|
+
},
|
|
34
|
+
attrSet: function () {
|
|
35
|
+
throw new Error()
|
|
36
|
+
},
|
|
37
|
+
attrGet: function (): string | null {
|
|
38
|
+
throw new Error()
|
|
39
|
+
},
|
|
40
|
+
attrJoin: function () {
|
|
41
|
+
throw new Error()
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe('MarkdownRender', () => {
|
|
48
|
+
it('renders correctly with default props', () => {
|
|
49
|
+
const wrapper = mount(MarkdownRender, {
|
|
50
|
+
props: {
|
|
51
|
+
source: '# Hello World\nThis is a **markdown** test.'
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
expect(wrapper.html()).toMatchSnapshot()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('renders correctly with custom markdown-it plugins', () => {
|
|
58
|
+
const wrapper = mount(MarkdownRender, {
|
|
59
|
+
props: {
|
|
60
|
+
source: '',
|
|
61
|
+
plugins: [mockPlugin]
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
expect(mockPlugin).toHaveBeenCalled()
|
|
66
|
+
expect(wrapper.props('plugins')).toHaveLength(1)
|
|
67
|
+
})
|
|
68
|
+
})
|
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
.markdown-renderer {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
2
|
+
pre,
|
|
3
|
+
code {
|
|
4
|
+
white-space: pre-wrap;
|
|
5
|
+
overflow-wrap: anywhere;
|
|
6
|
+
overflow-x: auto;
|
|
7
|
+
}
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
pre {
|
|
10
|
+
position: relative;
|
|
11
|
+
background-color: #f5f5f5;
|
|
12
|
+
border: 1px solid #e0e0e0;
|
|
13
|
+
border-radius: 6px;
|
|
14
|
+
padding: 0.75rem 1rem;
|
|
15
|
+
font-size: 0.875rem;
|
|
16
|
+
}
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
code {
|
|
19
|
+
padding: 0.2rem 0.4rem;
|
|
20
|
+
border-radius: 4px;
|
|
21
|
+
font-size: 0.875rem;
|
|
22
|
+
}
|
|
24
23
|
}
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
2
|
+
<div class="pulsing-dots">
|
|
3
|
+
<span class="generating-text">
|
|
4
|
+
{{ texts.generatingResponse }}
|
|
5
|
+
</span>
|
|
6
|
+
<div class="dots-container">
|
|
7
|
+
<span
|
|
8
|
+
v-for="(_, index) in dots"
|
|
9
|
+
:key="index"
|
|
10
|
+
class="dot"
|
|
11
|
+
:style="{ animationDelay: index * 0.2 + 's' }">
|
|
12
|
+
•
|
|
13
|
+
</span>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
16
|
</template>
|
|
17
17
|
|
|
18
18
|
<script setup lang="ts">
|
|
19
|
-
|
|
19
|
+
import { useTexts } from '@/composables/useTexts'
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
const dots = [1, 2, 3]
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
const texts = useTexts()
|
|
24
24
|
</script>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Components
|
|
2
|
+
import PulseDots from '../PulseDots.vue'
|
|
3
|
+
|
|
4
|
+
// Utils
|
|
5
|
+
import { mount } from '@vue/test-utils'
|
|
6
|
+
import { describe, it, expect } from 'vitest'
|
|
7
|
+
import { useTexts } from '@/composables/useTexts'
|
|
8
|
+
|
|
9
|
+
describe('PulseDots', () => {
|
|
10
|
+
it('renders correctly', () => {
|
|
11
|
+
const wrapper = mount(PulseDots)
|
|
12
|
+
expect(wrapper.html()).toMatchSnapshot()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('renders the correct number of dots', () => {
|
|
16
|
+
const wrapper = mount(PulseDots)
|
|
17
|
+
const dots = wrapper.findAll('.dot')
|
|
18
|
+
expect(dots.length).toBe(3)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('uses the correct generating text', () => {
|
|
22
|
+
const { generatingResponse } = useTexts()
|
|
23
|
+
const wrapper = mount(PulseDots)
|
|
24
|
+
expect(wrapper.text()).toContain(generatingResponse)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('applies correct animation delays to dots', () => {
|
|
28
|
+
const wrapper = mount(PulseDots)
|
|
29
|
+
const dots = wrapper.findAll('.dot')
|
|
30
|
+
|
|
31
|
+
dots.forEach((dot, index) => {
|
|
32
|
+
expect(dot.attributes('style')).toContain(`animation-delay: ${index * 0.2}s`)
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`PulseDots > renders correctly 1`] = `
|
|
4
|
+
"<div class="pulsing-dots"><span class="generating-text">Generating</span>
|
|
5
|
+
<div class="dots-container"><span class="dot" style="animation-delay: 0s;"> • </span><span class="dot" style="animation-delay: 0.2s;"> • </span><span class="dot" style="animation-delay: 0.4s;"> • </span></div>
|
|
6
|
+
</div>"
|
|
7
|
+
`;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`PulseDots > renders correctly 1`] = `
|
|
4
|
+
"<div class="pulsing-dots"><span class="generating-text">Generating</span>
|
|
5
|
+
<div class="dots-container"><span class="dot" style="animation-delay: 0s;"> • </span><span class="dot" style="animation-delay: 0.2s;"> • </span><span class="dot" style="animation-delay: 0.4s;"> • </span></div>
|
|
6
|
+
</div>"
|
|
7
|
+
`;
|
|
@@ -1,37 +1,38 @@
|
|
|
1
1
|
.pulsing-dots {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
justify-content: center;
|
|
5
|
+
flex-direction: row;
|
|
6
|
+
gap: 0.25rem;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
.generating-text {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
font-size: 0.9rem;
|
|
11
|
+
color: var(--q-theme-primary);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
.dots-container {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
gap: 0.1rem;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
.dot {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
font-size: 16px;
|
|
22
|
+
line-height: 1;
|
|
23
|
+
animation: pulse 1s infinite;
|
|
24
|
+
color: var(--q-theme-primary);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
@keyframes pulse {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
28
|
+
0%,
|
|
29
|
+
100% {
|
|
30
|
+
transform: scale(0.8);
|
|
31
|
+
opacity: 0.6;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
50% {
|
|
35
|
+
transform: scale(1);
|
|
36
|
+
opacity: 1;
|
|
37
|
+
}
|
|
37
38
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Composable
|
|
2
|
+
import { useChatMessages } from '../useChatMessages'
|
|
3
|
+
|
|
4
|
+
// Utils
|
|
5
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
6
|
+
|
|
7
|
+
const { addChatMessage, clearMessages, getMessages, getLastMessage, deleteMessageById } =
|
|
8
|
+
useChatMessages()
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
// Reset chat messages before each test
|
|
12
|
+
clearMessages()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
describe('useChatMessages', () => {
|
|
16
|
+
it('should add a chat message', () => {
|
|
17
|
+
addChatMessage('Hello, world!', 'user')
|
|
18
|
+
|
|
19
|
+
const messages = getMessages()
|
|
20
|
+
expect(messages.length).toBe(1)
|
|
21
|
+
expect(messages[0].message).toBe('Hello, world!')
|
|
22
|
+
expect(messages[0].sender).toBe('user')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('should set default sender to bot', () => {
|
|
26
|
+
addChatMessage('Hello, world!')
|
|
27
|
+
const message = getLastMessage()
|
|
28
|
+
|
|
29
|
+
expect(message).toBeDefined()
|
|
30
|
+
expect(message?.sender).toBe('bot')
|
|
31
|
+
expect(message?.message).toBe('Hello, world!')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('should get the last message', () => {
|
|
35
|
+
addChatMessage('First message', 'bot')
|
|
36
|
+
addChatMessage('Second message', 'user')
|
|
37
|
+
|
|
38
|
+
const lastMessage = getLastMessage()
|
|
39
|
+
expect(lastMessage).toBeDefined()
|
|
40
|
+
expect(lastMessage?.message).toBe('Second message')
|
|
41
|
+
expect(lastMessage?.sender).toBe('user')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('should clear all messages', () => {
|
|
45
|
+
addChatMessage('Hello, world!', 'user')
|
|
46
|
+
clearMessages()
|
|
47
|
+
|
|
48
|
+
const messages = getMessages()
|
|
49
|
+
expect(messages).toEqual([])
|
|
50
|
+
expect(messages.length).toBe(0)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('should delete a message by ID', () => {
|
|
54
|
+
const msg1 = addChatMessage('First message', 'bot')
|
|
55
|
+
const msg2 = addChatMessage('Second message', 'user')
|
|
56
|
+
|
|
57
|
+
expect(getMessages().length).toBe(2)
|
|
58
|
+
deleteMessageById(msg1.id)
|
|
59
|
+
|
|
60
|
+
const messages = getMessages()
|
|
61
|
+
expect(messages.length).toBe(1)
|
|
62
|
+
expect(messages[0].id).toBe(msg2.id)
|
|
63
|
+
})
|
|
64
|
+
})
|