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:
2026-10-17 22:17:32 +08:00
parent 4baccd5d88
commit effc29085c
20 changed files with 1356 additions and 819 deletions
+19 -11
View File
@@ -3,6 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import { flushPromises, mount } from '@vue/test-utils'
import { createTestingPinia } from '@pinia/testing'
import { defineComponent } from 'vue'
import ArcoVue from '@arco-design/web-vue'
import { useChatStore } from '@/stores/hermes/chat'
import VoiceDialogueControls from '@/components/hermes/chat/VoiceDialogueControls.vue'
import ChatInput from '@/components/hermes/chat/ChatInput.vue'
@@ -73,16 +74,23 @@ vi.mock('vue-i18n', () => ({
}),
}))
vi.mock('naive-ui', () => ({
NButton: { template: '<button type="button" v-bind="$attrs"><slot /><slot name="icon" /></button>' },
NTooltip: { template: '<div><slot name="trigger" /><slot /></div>' },
NSwitch: { template: '<button type="button"></button>' },
NDropdown: { template: '<div><slot /></div>' },
NModal: { template: '<div><slot /><slot name="footer" /></div>' },
NInputNumber: { template: '<input />' },
NPopselect: { template: '<div><slot /></div>' },
useMessage: () => ({ error: vi.fn(), success: vi.fn() }),
}))
// Arco's imperative message API needs a live DOM; the rest renders for real.
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(),
},
}
})
vi.mock('@/api/hermes/sessions', () => ({
fetchContextLength: vi.fn().mockResolvedValue(256000),
@@ -174,7 +182,7 @@ describe('VoiceDialogueControls', () => {
const wrapper = mount(ChatInput, {
...options,
global: {
plugins: [pinia],
plugins: [pinia, ArcoVue],
...options.global,
},
})