Files
hermes-web-ui/tests/client/sidebar-search.test.ts
2026-05-19 08:33:53 +08:00

161 lines
4.3 KiB
TypeScript

// @vitest-environment jsdom
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { mount } from '@vue/test-utils'
const openSessionSearchMock = vi.hoisted(() => vi.fn())
const mockAppStore = vi.hoisted(() => ({
sidebarOpen: true,
sidebarCollapsed: false,
connected: true,
serverVersion: 'test',
latestVersion: '',
updateAvailable: false,
clientOutdated: false,
updating: false,
toggleSidebar: vi.fn(),
toggleSidebarCollapsed: vi.fn(),
closeSidebar: vi.fn(),
doUpdate: vi.fn(),
reloadClient: vi.fn(),
}))
vi.mock('@/composables/useSessionSearch', () => ({
useSessionSearch: () => ({
openSessionSearch: openSessionSearchMock,
}),
}))
vi.mock('@/stores/hermes/app', () => ({
useAppStore: () => mockAppStore,
}))
vi.mock('vue-router', async (importOriginal) => {
const actual = await importOriginal<any>()
return {
...actual,
useRoute: () => ({ name: 'hermes.chat' }),
useRouter: () => ({ push: vi.fn() }),
}
})
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key: string) => key,
}),
createI18n: () => ({
global: { locale: { value: 'en' }, setLocaleMessage: vi.fn() },
}),
}))
vi.mock('@/composables/useTheme', () => ({
useTheme: () => ({ isDark: false }),
}))
vi.mock('/logo.png', () => ({
default: 'logo.png',
}))
vi.mock('naive-ui', async () => {
const actual = await vi.importActual<any>('naive-ui')
return {
...actual,
useMessage: () => ({
success: vi.fn(),
error: vi.fn(),
}),
NButton: {
template: '<button v-bind="$attrs"><slot /></button>',
},
NSelect: {
template: '<div />',
},
}
})
import AppSidebar from '@/components/layout/AppSidebar.vue'
describe('AppSidebar search entry', () => {
beforeEach(() => {
openSessionSearchMock.mockClear()
mockAppStore.serverVersion = 'test'
mockAppStore.latestVersion = ''
mockAppStore.updateAvailable = false
mockAppStore.clientOutdated = false
mockAppStore.updating = false
mockAppStore.sidebarCollapsed = false
mockAppStore.reloadClient.mockClear()
})
it('opens the session search modal from the sidebar button', async () => {
const wrapper = mount(AppSidebar, {
global: {
stubs: {
ProfileSelector: true,
ModelSelector: true,
LanguageSwitch: true,
ThemeSwitch: true,
NButton: true,
},
},
})
const buttons = wrapper.findAll('button')
const searchButton = buttons.find(node => node.text().includes('sidebar.search'))
expect(searchButton).toBeTruthy()
await searchButton!.trigger('click')
expect(openSessionSearchMock).toHaveBeenCalledTimes(1)
})
it('offers a client reload when the server version differs from the loaded bundle', async () => {
mockAppStore.clientOutdated = true
mockAppStore.serverVersion = '0.5.17'
const wrapper = mount(AppSidebar, {
global: {
stubs: {
ProfileSelector: true,
ModelSelector: true,
LanguageSwitch: true,
ThemeSwitch: true,
},
},
})
const reloadButton = wrapper.findAll('button')
.find(node => node.text().includes('sidebar.reloadClientVersion'))
expect(reloadButton).toBeTruthy()
await reloadButton!.trigger('click')
expect(mockAppStore.reloadClient).toHaveBeenCalledTimes(1)
})
it('uses short group labels and keeps group folding active when collapsed', async () => {
mockAppStore.sidebarCollapsed = true
const wrapper = mount(AppSidebar, {
global: {
stubs: {
ProfileSelector: true,
ModelSelector: true,
LanguageSwitch: true,
ThemeSwitch: true,
NButton: true,
},
},
})
expect(wrapper.classes()).toContain('collapsed')
expect(wrapper.findAll('.nav-group-label span').map(node => node.text())).toEqual([
'sidebar.groupConversationShort',
'sidebar.groupAgentShort',
'sidebar.groupMonitoringShort',
'sidebar.groupSystemShort',
])
const agentGroup = wrapper.findAll('.nav-group')[1]
expect(agentGroup.find('.nav-group-items').attributes('style')).toBeUndefined()
await agentGroup.find('.nav-group-label').trigger('click')
expect(agentGroup.find('.nav-group-items').attributes('style')).toContain('display: none')
})
})