Phase 006: migrate the chat page from naive-ui to Arco Design Vue
Swaps the rendering layer only. Chat store logic, keyboard handling, and the /chat-run Socket.IO contract are untouched, so a mixed UI cannot hide a chat regression behind a library swap. Arco's dropdowns take their items from a #content slot of a-doption children rather than a droplist prop, and Arco Triggers cannot be nested, so the two toolbar menus lost their a-tooltip in favour of a native title. Both rules are recorded in docs/arco-mapping.md, and tests/e2e/chat-input-dropdown.spec.ts plus tests/e2e/chat-rendering-smoke.spec.ts keep the menus, the streaming markdown rendering, and the no-Arco-inside-the-bubble red line from regressing silently.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
// @vitest-environment jsdom
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { config, mount } from '@vue/test-utils'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
const mermaidMock = vi.hoisted(() => ({
|
||||
@@ -33,30 +33,44 @@ vi.mock('vue-i18n', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('naive-ui', () => ({
|
||||
NDrawer: {
|
||||
props: ['show', 'width'],
|
||||
template: '<div v-if="show" class="n-drawer-stub" :data-width="width"><slot /></div>',
|
||||
},
|
||||
NDrawerContent: {
|
||||
props: {
|
||||
title: { type: String, default: '' },
|
||||
closable: { type: Boolean, default: false },
|
||||
bodyContentStyle: { type: [Object, String], default: undefined },
|
||||
// The text preview drawer teleports to body in jsdom, so stub the overlays to
|
||||
// render inline with the same DOM shape the component expects.
|
||||
vi.mock('@arco-design/web-vue', async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import('@arco-design/web-vue')>(
|
||||
'@arco-design/web-vue',
|
||||
)
|
||||
return {
|
||||
...actual,
|
||||
Message: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
warning: vi.fn(),
|
||||
info: vi.fn(),
|
||||
loading: vi.fn(),
|
||||
},
|
||||
template: '<section class="n-drawer-content-stub" :data-body-padding="bodyContentStyle && bodyContentStyle.padding"><header class="n-drawer-header-stub">{{ title }}<button v-if="closable" class="n-drawer-close-stub" @click="$emit(\'close\')">x</button></header><slot /></section>',
|
||||
}
|
||||
})
|
||||
|
||||
config.global.stubs = {
|
||||
ADrawer: {
|
||||
name: 'ADrawer',
|
||||
props: ['visible', 'width'],
|
||||
emits: ['update:visible'],
|
||||
template: `
|
||||
<div v-if="visible" class="a-drawer-stub" :data-width="width">
|
||||
<header class="a-drawer-title-stub"><slot name="title" /></header>
|
||||
<button class="a-drawer-close-stub" @click="$emit('update:visible', false)">x</button>
|
||||
<slot />
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
NSpin: {
|
||||
props: ['show'],
|
||||
template: '<div class="n-spin-stub"><slot /></div>',
|
||||
ASpin: {
|
||||
name: 'ASpin',
|
||||
props: ['loading'],
|
||||
template: '<div class="a-spin-stub"><slot /></div>',
|
||||
},
|
||||
useMessage: () => ({
|
||||
error: vi.fn(),
|
||||
success: vi.fn(),
|
||||
warning: vi.fn(),
|
||||
info: vi.fn(),
|
||||
}),
|
||||
}))
|
||||
}
|
||||
|
||||
vi.mock('@/api/hermes/download', () => ({
|
||||
downloadFile: downloadApiMock.downloadFile,
|
||||
@@ -316,7 +330,7 @@ describe('MarkdownRenderer', () => {
|
||||
expect(downloadApiMock.downloadFile).toHaveBeenCalledTimes(1)
|
||||
expect(downloadApiMock.downloadFile).toHaveBeenCalledWith('/tmp/notes.txt', 'notes.txt')
|
||||
expect(downloadApiMock.fetchFileText).not.toHaveBeenCalled()
|
||||
expect(wrapper.find('.n-drawer-stub').exists()).toBe(false)
|
||||
expect(wrapper.find('.a-drawer-stub').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('opens text previews in a responsive drawer with a close control', async () => {
|
||||
@@ -330,17 +344,16 @@ describe('MarkdownRenderer', () => {
|
||||
await Promise.resolve()
|
||||
await nextTick()
|
||||
|
||||
const drawer = wrapper.find('.n-drawer-stub')
|
||||
const drawer = wrapper.find('.a-drawer-stub')
|
||||
expect(drawer.exists()).toBe(true)
|
||||
expect(drawer.attributes('data-width')).toBe('min(800px, 100vw)')
|
||||
expect(drawer.find('.n-drawer-content-stub').attributes('data-body-padding')).toBe('0')
|
||||
expect(drawer.text()).toContain('download.contentDisplay')
|
||||
expect(downloadApiMock.fetchFileText).toHaveBeenCalledWith('/tmp/notes.txt', 'notes.txt')
|
||||
|
||||
await drawer.find('.n-drawer-close-stub').trigger('click')
|
||||
await drawer.find('.a-drawer-close-stub').trigger('click')
|
||||
await nextTick()
|
||||
|
||||
expect(wrapper.find('.n-drawer-stub').exists()).toBe(false)
|
||||
expect(wrapper.find('.a-drawer-stub').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('renders markdown file previews as markdown content', async () => {
|
||||
@@ -355,7 +368,7 @@ describe('MarkdownRenderer', () => {
|
||||
await Promise.resolve()
|
||||
await nextTick()
|
||||
|
||||
const drawer = wrapper.find('.n-drawer-stub')
|
||||
const drawer = wrapper.find('.a-drawer-stub')
|
||||
expect(drawer.exists()).toBe(true)
|
||||
expect(drawer.find('.text-preview-markdown').exists()).toBe(true)
|
||||
expect(drawer.find('.text-preview-body').exists()).toBe(false)
|
||||
|
||||
Reference in New Issue
Block a user