initialize Electron desktop app and remove unused files
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
// @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: '',
|
||||
isDocker: false,
|
||||
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(), hasRoute: () => true }),
|
||||
}
|
||||
})
|
||||
|
||||
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('@/components/layout/ProfileSelector.vue', () => ({
|
||||
default: { name: 'ProfileSelector', template: '<div />' },
|
||||
}))
|
||||
|
||||
vi.mock('@/components/layout/ModelSelector.vue', () => ({
|
||||
default: { name: 'ModelSelector', template: '<div />' },
|
||||
}))
|
||||
|
||||
vi.mock('@/components/layout/LanguageSwitch.vue', () => ({
|
||||
default: { name: 'LanguageSwitch', template: '<div />' },
|
||||
}))
|
||||
|
||||
vi.mock('@/components/common/RouteLinkItem.vue', () => ({
|
||||
default: {
|
||||
name: 'RouteLinkItem',
|
||||
props: ['to', 'active'],
|
||||
template: '<a class="route-link-item" :class="{ active }" href="#"><slot /></a>',
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('naive-ui', () => ({
|
||||
// AppSidebar no longer imports naive-ui; keep the module inert for any
|
||||
// transitive import during the Arco migration.
|
||||
useMessage: () => ({
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
}),
|
||||
}))
|
||||
|
||||
import AppSidebar from '@/components/layout/AppSidebar.vue'
|
||||
import { config } from '@vue/test-utils'
|
||||
|
||||
// Arco components are globally registered in main.ts; register test stubs
|
||||
// here with the same contract the sidebar relies on.
|
||||
config.global.stubs = {
|
||||
'a-button': {
|
||||
template: '<button class="a-button-stub" v-bind="$attrs"><slot /></button>',
|
||||
},
|
||||
'a-modal': {
|
||||
props: ['visible'],
|
||||
emits: ['update:visible'],
|
||||
template: '<div v-if="visible" class="a-modal-stub"><slot /></div>',
|
||||
},
|
||||
'a-tag': {
|
||||
template: '<span class="a-tag-stub"><slot /></span>',
|
||||
},
|
||||
}
|
||||
|
||||
function fakeJwt(payload: Record<string, unknown>) {
|
||||
return `header.${btoa(JSON.stringify(payload)).replace(/=/g, '')}.signature`
|
||||
}
|
||||
|
||||
describe('AppSidebar navigation', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear()
|
||||
openSessionSearchMock.mockClear()
|
||||
mockAppStore.serverVersion = 'test'
|
||||
mockAppStore.latestVersion = ''
|
||||
mockAppStore.isDocker = false
|
||||
mockAppStore.updateAvailable = false
|
||||
mockAppStore.clientOutdated = false
|
||||
mockAppStore.updating = false
|
||||
mockAppStore.sidebarCollapsed = false
|
||||
mockAppStore.reloadClient.mockClear()
|
||||
mockAppStore.doUpdate.mockReset()
|
||||
mockAppStore.doUpdate.mockResolvedValue(false)
|
||||
})
|
||||
|
||||
it('keeps page-sidebar-only actions out of the app sidebar', () => {
|
||||
const wrapper = mount(AppSidebar, {
|
||||
global: {
|
||||
stubs: {
|
||||
ProfileSelector: true,
|
||||
ModelSelector: true,
|
||||
LanguageSwitch: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.text()).not.toContain('sidebar.search')
|
||||
expect(wrapper.text()).not.toContain('sidebar.reloadClientVersion')
|
||||
expect(wrapper.find('.sidebar-return-tab').exists()).toBe(true)
|
||||
})
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.classes()).toContain('collapsed')
|
||||
expect(wrapper.findAll('.nav-group-label span').map(node => node.text())).toEqual([
|
||||
'sidebar.groupAgentShort',
|
||||
'sidebar.groupMonitoringShort',
|
||||
'sidebar.groupToolsShort',
|
||||
'sidebar.groupSystemShort',
|
||||
])
|
||||
|
||||
const agentGroup = wrapper.findAll('.nav-group')[0]
|
||||
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')
|
||||
})
|
||||
|
||||
it('keeps MCP visible for admins while hiding device management', () => {
|
||||
localStorage.setItem('hermes_api_key', fakeJwt({ sub: '2', role: 'admin' }))
|
||||
const wrapper = mount(AppSidebar, {
|
||||
global: {
|
||||
stubs: {
|
||||
ProfileSelector: true,
|
||||
ModelSelector: true,
|
||||
LanguageSwitch: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.text()).toContain('sidebar.mcp')
|
||||
expect(wrapper.text()).not.toContain('sidebar.devices')
|
||||
})
|
||||
|
||||
it('uses the regular update button to open Docker upgrade guidance', async () => {
|
||||
mockAppStore.isDocker = true
|
||||
mockAppStore.updateAvailable = true
|
||||
mockAppStore.latestVersion = '0.6.29'
|
||||
const wrapper = mount(AppSidebar, {
|
||||
global: {
|
||||
stubs: {
|
||||
ProfileSelector: true,
|
||||
ModelSelector: true,
|
||||
LanguageSwitch: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const button = wrapper.get('.update-btn')
|
||||
expect(button.classes()).not.toContain('docker-update-btn')
|
||||
expect(button.text()).toContain('sidebar.updateVersion')
|
||||
|
||||
await button.trigger('click')
|
||||
|
||||
expect(mockAppStore.doUpdate).not.toHaveBeenCalled()
|
||||
expect(wrapper.text()).toContain('sidebar.dockerUpdateGuide')
|
||||
})
|
||||
|
||||
it('keeps the original npm update action outside Docker', async () => {
|
||||
mockAppStore.isDocker = false
|
||||
mockAppStore.updateAvailable = true
|
||||
mockAppStore.latestVersion = '0.6.29'
|
||||
const wrapper = mount(AppSidebar, {
|
||||
global: {
|
||||
stubs: {
|
||||
ProfileSelector: true,
|
||||
ModelSelector: true,
|
||||
LanguageSwitch: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await wrapper.get('.update-btn').trigger('click')
|
||||
|
||||
expect(mockAppStore.doUpdate).toHaveBeenCalledOnce()
|
||||
expect(wrapper.text()).not.toContain('sidebar.dockerUpdateGuide')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user