// @vitest-environment jsdom import { beforeEach, describe, expect, it, vi } from 'vitest' import { createPinia, setActivePinia } from 'pinia' const mockSystemApi = vi.hoisted(() => ({ checkHealth: vi.fn(), fetchAvailableModels: vi.fn(), updateDefaultModel: vi.fn(), updateModelVisibility: vi.fn(), triggerUpdate: vi.fn(), })) vi.mock('@/api/hermes/system', () => mockSystemApi) import { useAppStore } from '@/stores/hermes/app' describe('App Store', () => { beforeEach(() => { setActivePinia(createPinia()) vi.clearAllMocks() window.localStorage.clear() }) it('persists desktop sidebar collapsed state to localStorage', () => { const store = useAppStore() expect(store.sidebarCollapsed).toBe(false) store.toggleSidebarCollapsed() expect(store.sidebarCollapsed).toBe(true) expect(window.localStorage.getItem('hermes_sidebar_collapsed')).toBe('1') store.toggleSidebarCollapsed() expect(store.sidebarCollapsed).toBe(false) expect(window.localStorage.getItem('hermes_sidebar_collapsed')).toBe('0') }) it('loads model visibility and falls back when the configured default is hidden', async () => { mockSystemApi.fetchAvailableModels.mockResolvedValue({ default: 'deepseek-chat', default_provider: 'deepseek', groups: [ { provider: 'deepseek', label: 'DeepSeek', base_url: 'https://api.deepseek.com/v1', api_key: 'sk-test', models: ['deepseek-reasoner'], }, ], allProviders: [], model_visibility: { deepseek: { mode: 'include', models: ['deepseek-reasoner'] }, }, }) const store = useAppStore() await store.loadModels() expect(store.modelVisibility).toEqual({ deepseek: { mode: 'include', models: ['deepseek-reasoner'] }, }) expect(store.selectedModel).toBe('deepseek-reasoner') expect(store.selectedProvider).toBe('deepseek') expect(store.isModelVisible('deepseek', 'deepseek-reasoner')).toBe(true) expect(store.isModelVisible('deepseek', 'deepseek-chat')).toBe(false) }) it('persists model visibility without changing the canonical selected model id', async () => { mockSystemApi.fetchAvailableModels.mockResolvedValue({ default: 'deepseek-reasoner', default_provider: 'deepseek', groups: [ { provider: 'deepseek', label: 'DeepSeek', base_url: 'https://api.deepseek.com/v1', api_key: 'sk-test', models: ['deepseek-reasoner'], }, ], allProviders: [], model_visibility: { deepseek: { mode: 'include', models: ['deepseek-reasoner'] }, }, }) mockSystemApi.updateModelVisibility.mockResolvedValue({ success: true, model_visibility: { deepseek: { mode: 'include', models: ['deepseek-reasoner'] }, }, }) const store = useAppStore() await store.setModelVisibility('deepseek', { mode: 'include', models: ['deepseek-reasoner'] }) expect(mockSystemApi.updateModelVisibility).toHaveBeenCalledWith({ provider: 'deepseek', mode: 'include', models: ['deepseek-reasoner'], }) expect(store.selectedModel).toBe('deepseek-reasoner') expect(store.selectedProvider).toBe('deepseek') expect(mockSystemApi.updateDefaultModel).not.toHaveBeenCalled() }) it('clears the updating state and reports failure when self-update request fails', async () => { const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) mockSystemApi.triggerUpdate.mockRejectedValue(new Error('install failed')) const store = useAppStore() const ok = await store.doUpdate() expect(ok).toBe(false) expect(store.updating).toBe(false) expect(consoleError).toHaveBeenCalledWith('Failed to update Hermes Web UI:', expect.any(Error)) consoleError.mockRestore() }) })