Files
zclaw_openfang/tests/desktop/store/configStore.test.ts
iven c19be048e4 feat(phase-12-13): complete performance optimization and test coverage
Phase 12 - Performance Optimization:
- Add message-virtualization.ts with useVirtualizedMessages hook
- Implement MessageCache<T> LRU cache for rendered content
- Add createMessageBatcher for WebSocket message batching
- Add calculateVisibleRange and debounced scroll handlers
- Support for 10,000+ messages without performance degradation

Phase 13 - Test Coverage:
- Add workflowStore.test.ts (28 tests)
- Add configStore.test.ts (40 tests)
- Update general-settings.test.tsx to match current UI
- Total tests: 148 passing

Code Quality:
- TypeScript compilation passes
- All 148 tests pass

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-15 20:35:16 +08:00

735 lines
26 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
import type {
ConfigStoreClient,
QuickConfig,
ChannelInfo,
SkillInfo,
ScheduledTask,
} from '../../../desktop/src/store/configStore';
// Mock client with all config methods
const createMockClient = (): ConfigStoreClient => ({
getWorkspaceInfo: vi.fn(),
getQuickConfig: vi.fn(),
saveQuickConfig: vi.fn(),
listSkills: vi.fn(),
getSkill: vi.fn(),
createSkill: vi.fn(),
updateSkill: vi.fn(),
deleteSkill: vi.fn(),
listChannels: vi.fn(),
getChannel: vi.fn(),
createChannel: vi.fn(),
updateChannel: vi.fn(),
deleteChannel: vi.fn(),
listScheduledTasks: vi.fn(),
createScheduledTask: vi.fn(),
listModels: vi.fn(),
getFeishuStatus: vi.fn(),
});
let mockClient: ConfigStoreClient;
function resetMocks() {
vi.clearAllMocks();
mockClient = createMockClient();
mockClient.getWorkspaceInfo = vi.fn().mockResolvedValue({
path: '~/.openfang/workspace',
resolvedPath: '/home/user/.openfang/workspace',
exists: true,
fileCount: 42,
totalSize: 1024000,
});
mockClient.getQuickConfig = vi.fn().mockResolvedValue({
quickConfig: {
agentName: 'ZCLAW',
theme: 'dark',
gatewayUrl: 'ws://127.0.0.1:4200/ws',
workspaceDir: '~/.openfang/workspace',
},
});
mockClient.saveQuickConfig = vi.fn().mockImplementation((config: QuickConfig) => ({
quickConfig: config,
}));
mockClient.listSkills = vi.fn().mockResolvedValue({
skills: [
{ id: 'builtin:translation', name: 'translation', path: '/skills/translation/SKILL.md', source: 'builtin' },
{ id: 'custom:summarize', name: 'summarize', path: '/custom/summarize/SKILL.md', source: 'extra' },
],
extraDirs: ['/custom-skills'],
});
mockClient.getSkill = vi.fn().mockImplementation((id: string) => ({
skill: { id, name: id.split(':')[1], path: `/skills/${id}/SKILL.md`, source: 'builtin' },
}));
mockClient.createSkill = vi.fn().mockImplementation((skill) => ({
skill: { id: `custom:${skill.name}`, ...skill, source: 'extra' as const, path: `/custom/${skill.name}/SKILL.md` },
}));
mockClient.updateSkill = vi.fn().mockImplementation((id: string, updates) => ({
skill: { id, name: updates.name || 'skill', path: `/skills/${id}/SKILL.md`, source: 'builtin' as const, ...updates },
}));
mockClient.deleteSkill = vi.fn().mockResolvedValue(undefined);
mockClient.listChannels = vi.fn().mockResolvedValue({
channels: [
{ id: 'feishu', type: 'feishu', label: 'Feishu', status: 'active', accounts: 2 },
{ id: 'slack', type: 'slack', label: 'Slack', status: 'inactive' },
],
});
mockClient.getChannel = vi.fn().mockImplementation((id: string) => ({
channel: { id, type: id, label: id.charAt(0).toUpperCase() + id.slice(1), status: 'active' as const },
}));
mockClient.createChannel = vi.fn().mockImplementation((channel) => ({
channel: { id: `channel_${Date.now()}`, ...channel, status: 'active' as const },
}));
mockClient.updateChannel = vi.fn().mockImplementation((id: string, updates) => ({
channel: { id, type: 'test', label: 'Test', status: 'active' as const, ...updates },
}));
mockClient.deleteChannel = vi.fn().mockResolvedValue(undefined);
mockClient.listScheduledTasks = vi.fn().mockResolvedValue({
tasks: [
{ id: 'task_1', name: 'Daily Report', schedule: '0 9 * * *', status: 'active' },
{ id: 'task_2', name: 'Weekly Backup', schedule: '0 0 * * 0', status: 'paused' },
],
});
mockClient.createScheduledTask = vi.fn().mockImplementation((task) => ({
id: `task_${Date.now()}`,
name: task.name,
schedule: task.schedule,
status: 'active' as const,
}));
mockClient.listModels = vi.fn().mockResolvedValue({
models: [
{ id: 'glm-4', name: 'GLM-4', provider: 'zhipuai' },
{ id: 'glm-5', name: 'GLM-5', provider: 'zhipuai' },
{ id: 'gpt-4', name: 'GPT-4', provider: 'openai' },
],
});
mockClient.getFeishuStatus = vi.fn().mockResolvedValue({ configured: true, accounts: 2 });
}
describe('configStore', () => {
beforeEach(async () => {
vi.resetModules();
resetMocks();
});
describe('initial state', () => {
it('initializes with default empty state', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
const state = useConfigStore.getState();
expect(state.quickConfig).toEqual({});
expect(state.workspaceInfo).toBeNull();
expect(state.channels).toEqual([]);
expect(state.scheduledTasks).toEqual([]);
expect(state.skillsCatalog).toEqual([]);
expect(state.models).toEqual([]);
expect(state.modelsLoading).toBe(false);
expect(state.modelsError).toBeNull();
expect(state.error).toBeNull();
expect(state.client).toBeNull();
});
});
describe('client injection', () => {
it('accepts a client via setConfigStoreClient', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
expect(useConfigStore.getState().client).toBe(mockClient);
});
});
describe('loadQuickConfig', () => {
it('loads quick config from the client', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
await useConfigStore.getState().loadQuickConfig();
expect(mockClient.getQuickConfig).toHaveBeenCalledTimes(1);
const state = useConfigStore.getState();
expect(state.quickConfig).toMatchObject({
agentName: 'ZCLAW',
theme: 'dark',
gatewayUrl: 'ws://127.0.0.1:4200/ws',
});
});
it('sets empty config when client returns null', async () => {
mockClient.getQuickConfig = vi.fn().mockResolvedValue(null);
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
await useConfigStore.getState().loadQuickConfig();
expect(useConfigStore.getState().quickConfig).toEqual({});
});
it('handles errors silently', async () => {
mockClient.getQuickConfig = vi.fn().mockRejectedValue(new Error('Config error'));
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
// Should not throw
await expect(useConfigStore.getState().loadQuickConfig()).resolves.toBeUndefined();
expect(useConfigStore.getState().quickConfig).toEqual({});
});
it('does nothing when client is not set', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
await useConfigStore.getState().loadQuickConfig();
expect(mockClient.getQuickConfig).not.toHaveBeenCalled();
});
});
describe('saveQuickConfig', () => {
it('merges updates with existing config and saves', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
useConfigStore.setState({
quickConfig: { agentName: 'ZCLAW', theme: 'dark' },
});
await useConfigStore.getState().saveQuickConfig({ theme: 'light', workspaceDir: '/new/path' });
expect(mockClient.saveQuickConfig).toHaveBeenCalledWith({
agentName: 'ZCLAW',
theme: 'light',
workspaceDir: '/new/path',
});
const state = useConfigStore.getState();
expect(state.quickConfig.theme).toBe('light');
expect(state.quickConfig.workspaceDir).toBe('/new/path');
expect(state.quickConfig.agentName).toBe('ZCLAW');
});
it('sets error when save fails', async () => {
mockClient.saveQuickConfig = vi.fn().mockRejectedValue(new Error('Save failed'));
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
await useConfigStore.getState().saveQuickConfig({ theme: 'light' });
expect(useConfigStore.getState().error).toBe('Save failed');
});
it('does nothing when client is not set', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
await useConfigStore.getState().saveQuickConfig({ theme: 'light' });
expect(mockClient.saveQuickConfig).not.toHaveBeenCalled();
});
});
describe('loadWorkspaceInfo', () => {
it('loads workspace info from the client', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
await useConfigStore.getState().loadWorkspaceInfo();
expect(mockClient.getWorkspaceInfo).toHaveBeenCalledTimes(1);
const state = useConfigStore.getState();
expect(state.workspaceInfo).toMatchObject({
path: '~/.openfang/workspace',
resolvedPath: '/home/user/.openfang/workspace',
exists: true,
fileCount: 42,
totalSize: 1024000,
});
});
it('handles errors silently', async () => {
mockClient.getWorkspaceInfo = vi.fn().mockRejectedValue(new Error('Workspace error'));
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
await expect(useConfigStore.getState().loadWorkspaceInfo()).resolves.toBeUndefined();
expect(useConfigStore.getState().workspaceInfo).toBeNull();
});
});
describe('loadChannels', () => {
it('loads channels from the client', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
await useConfigStore.getState().loadChannels();
expect(mockClient.listChannels).toHaveBeenCalledTimes(1);
const state = useConfigStore.getState();
expect(state.channels).toHaveLength(2);
expect(state.channels[0]).toMatchObject({
id: 'feishu',
type: 'feishu',
label: 'Feishu',
status: 'active',
accounts: 2,
});
});
it('falls back to probing feishu status when listChannels fails', async () => {
mockClient.listChannels = vi.fn().mockRejectedValue(new Error('Channels unavailable'));
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
await useConfigStore.getState().loadChannels();
expect(mockClient.getFeishuStatus).toHaveBeenCalledTimes(1);
const state = useConfigStore.getState();
expect(state.channels).toHaveLength(1);
expect(state.channels[0]).toMatchObject({
id: 'feishu',
type: 'feishu',
status: 'active',
});
});
it('handles feishu status errors gracefully', async () => {
mockClient.listChannels = vi.fn().mockRejectedValue(new Error('Channels unavailable'));
mockClient.getFeishuStatus = vi.fn().mockRejectedValue(new Error('Feishu error'));
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
await useConfigStore.getState().loadChannels();
const state = useConfigStore.getState();
expect(state.channels).toHaveLength(1);
expect(state.channels[0]).toMatchObject({
id: 'feishu',
status: 'inactive',
});
});
});
describe('getChannel', () => {
it('fetches and returns a single channel', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
const channel = await useConfigStore.getState().getChannel('feishu');
expect(mockClient.getChannel).toHaveBeenCalledWith('feishu');
expect(channel).toMatchObject({
id: 'feishu',
status: 'active',
});
});
it('updates existing channel in state', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
useConfigStore.setState({
channels: [{ id: 'feishu', type: 'feishu', label: 'Feishu', status: 'inactive' }],
});
await useConfigStore.getState().getChannel('feishu');
const state = useConfigStore.getState();
expect(state.channels[0].status).toBe('active');
});
it('sets error on failure', async () => {
mockClient.getChannel = vi.fn().mockRejectedValue(new Error('Channel error'));
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
const result = await useConfigStore.getState().getChannel('unknown');
expect(result).toBeUndefined();
expect(useConfigStore.getState().error).toBe('Channel error');
});
});
describe('createChannel', () => {
it('creates a channel and adds it to state', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
const channel = await useConfigStore.getState().createChannel({
type: 'discord',
name: 'Discord Bot',
config: { webhook: 'https://discord.com/...' },
enabled: true,
});
expect(mockClient.createChannel).toHaveBeenCalled();
expect(channel).toBeDefined();
expect(channel?.status).toBe('active');
const state = useConfigStore.getState();
expect(state.channels).toHaveLength(1);
});
it('sets error on create failure', async () => {
mockClient.createChannel = vi.fn().mockRejectedValue(new Error('Create failed'));
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
const result = await useConfigStore.getState().createChannel({
type: 'test',
name: 'Test',
config: {},
});
expect(result).toBeUndefined();
expect(useConfigStore.getState().error).toBe('Create failed');
});
});
describe('updateChannel', () => {
it('updates a channel in state', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
useConfigStore.setState({
channels: [{ id: 'feishu', type: 'feishu', label: 'Feishu', status: 'inactive' }],
});
const result = await useConfigStore.getState().updateChannel('feishu', {
name: 'Updated Feishu',
enabled: true,
});
expect(mockClient.updateChannel).toHaveBeenCalledWith('feishu', expect.objectContaining({ name: 'Updated Feishu' }));
expect(result).toBeDefined();
const state = useConfigStore.getState();
expect(state.channels[0].status).toBe('active');
});
it('sets error on update failure', async () => {
mockClient.updateChannel = vi.fn().mockRejectedValue(new Error('Update failed'));
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
const result = await useConfigStore.getState().updateChannel('unknown', { name: 'Test' });
expect(result).toBeUndefined();
expect(useConfigStore.getState().error).toBe('Update failed');
});
});
describe('deleteChannel', () => {
it('deletes a channel from state', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
useConfigStore.setState({
channels: [
{ id: 'feishu', type: 'feishu', label: 'Feishu', status: 'active' },
{ id: 'slack', type: 'slack', label: 'Slack', status: 'inactive' },
],
});
await useConfigStore.getState().deleteChannel('feishu');
expect(mockClient.deleteChannel).toHaveBeenCalledWith('feishu');
const state = useConfigStore.getState();
expect(state.channels).toHaveLength(1);
expect(state.channels.find(c => c.id === 'feishu')).toBeUndefined();
});
it('sets error on delete failure', async () => {
mockClient.deleteChannel = vi.fn().mockRejectedValue(new Error('Delete failed'));
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
await useConfigStore.getState().deleteChannel('unknown');
expect(useConfigStore.getState().error).toBe('Delete failed');
});
});
describe('loadSkillsCatalog', () => {
it('loads skills and extra dirs from the client', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
await useConfigStore.getState().loadSkillsCatalog();
expect(mockClient.listSkills).toHaveBeenCalledTimes(1);
const state = useConfigStore.getState();
expect(state.skillsCatalog).toHaveLength(2);
expect(state.skillsCatalog[0]).toMatchObject({
id: 'builtin:translation',
name: 'translation',
source: 'builtin',
});
expect(state.quickConfig.skillsExtraDirs).toEqual(['/custom-skills']);
});
it('handles errors silently', async () => {
mockClient.listSkills = vi.fn().mockRejectedValue(new Error('Skills error'));
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
await expect(useConfigStore.getState().loadSkillsCatalog()).resolves.toBeUndefined();
expect(useConfigStore.getState().skillsCatalog).toEqual([]);
});
});
describe('getSkill', () => {
it('fetches a single skill by id', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
const skill = await useConfigStore.getState().getSkill('builtin:translation');
expect(mockClient.getSkill).toHaveBeenCalledWith('builtin:translation');
expect(skill).toMatchObject({
id: 'builtin:translation',
name: 'translation',
});
});
it('returns undefined on error', async () => {
mockClient.getSkill = vi.fn().mockRejectedValue(new Error('Skill not found'));
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
const result = await useConfigStore.getState().getSkill('unknown');
expect(result).toBeUndefined();
});
});
describe('createSkill', () => {
it('creates a skill and adds it to catalog', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
const skill = await useConfigStore.getState().createSkill({
name: 'analyzer',
description: 'Analyzes content',
triggers: [{ type: 'keyword', pattern: 'analyze' }],
actions: [{ type: 'hand', params: { hand: 'analyzer' } }],
enabled: true,
});
expect(mockClient.createSkill).toHaveBeenCalled();
expect(skill).toBeDefined();
expect(skill?.id).toBe('custom:analyzer');
const state = useConfigStore.getState();
expect(state.skillsCatalog).toHaveLength(1);
});
});
describe('updateSkill', () => {
it('updates a skill in catalog', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
useConfigStore.setState({
skillsCatalog: [{ id: 'builtin:test', name: 'test', path: '/test', source: 'builtin' }],
});
const result = await useConfigStore.getState().updateSkill('builtin:test', {
name: 'updated',
description: 'Updated skill',
});
expect(mockClient.updateSkill).toHaveBeenCalledWith('builtin:test', expect.objectContaining({ name: 'updated' }));
expect(result).toBeDefined();
});
});
describe('deleteSkill', () => {
it('deletes a skill from catalog', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
useConfigStore.setState({
skillsCatalog: [
{ id: 'skill_1', name: 'Skill 1', path: '/s1', source: 'builtin' },
{ id: 'skill_2', name: 'Skill 2', path: '/s2', source: 'builtin' },
],
});
await useConfigStore.getState().deleteSkill('skill_1');
expect(mockClient.deleteSkill).toHaveBeenCalledWith('skill_1');
const state = useConfigStore.getState();
expect(state.skillsCatalog).toHaveLength(1);
expect(state.skillsCatalog.find(s => s.id === 'skill_1')).toBeUndefined();
});
it('handles errors silently', async () => {
mockClient.deleteSkill = vi.fn().mockRejectedValue(new Error('Delete failed'));
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
useConfigStore.setState({
skillsCatalog: [{ id: 'skill_1', name: 'Skill 1', path: '/s1', source: 'builtin' }],
});
// Should not throw
await expect(useConfigStore.getState().deleteSkill('skill_1')).resolves.toBeUndefined();
});
});
describe('loadScheduledTasks', () => {
it('loads scheduled tasks from the client', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
await useConfigStore.getState().loadScheduledTasks();
expect(mockClient.listScheduledTasks).toHaveBeenCalledTimes(1);
const state = useConfigStore.getState();
expect(state.scheduledTasks).toHaveLength(2);
expect(state.scheduledTasks[0]).toMatchObject({
id: 'task_1',
name: 'Daily Report',
schedule: '0 9 * * *',
status: 'active',
});
});
it('handles errors silently', async () => {
mockClient.listScheduledTasks = vi.fn().mockRejectedValue(new Error('Tasks error'));
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
await expect(useConfigStore.getState().loadScheduledTasks()).resolves.toBeUndefined();
expect(useConfigStore.getState().scheduledTasks).toEqual([]);
});
});
describe('createScheduledTask', () => {
it('creates a scheduled task and adds to state', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
const task = await useConfigStore.getState().createScheduledTask({
name: 'New Task',
schedule: '0 10 * * *',
scheduleType: 'cron',
target: { type: 'hand', id: 'echo' },
description: 'A new task',
enabled: true,
});
expect(mockClient.createScheduledTask).toHaveBeenCalled();
expect(task).toBeDefined();
expect(task?.status).toBe('active');
const state = useConfigStore.getState();
expect(state.scheduledTasks).toHaveLength(1);
});
it('sets error on create failure', async () => {
mockClient.createScheduledTask = vi.fn().mockRejectedValue(new Error('Create task failed'));
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
const result = await useConfigStore.getState().createScheduledTask({
name: 'Failed Task',
schedule: 'invalid',
scheduleType: 'cron',
});
expect(result).toBeUndefined();
expect(useConfigStore.getState().error).toBe('Create task failed');
});
});
describe('loadModels', () => {
it('loads models from the client', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
await useConfigStore.getState().loadModels();
expect(mockClient.listModels).toHaveBeenCalledTimes(1);
const state = useConfigStore.getState();
expect(state.models).toHaveLength(3);
expect(state.modelsLoading).toBe(false);
expect(state.modelsError).toBeNull();
});
it('sets modelsLoading during load', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
const loadPromise = useConfigStore.getState().loadModels();
await loadPromise;
expect(useConfigStore.getState().modelsLoading).toBe(false);
});
it('sets modelsError when load fails', async () => {
mockClient.listModels = vi.fn().mockRejectedValue(new Error('Models error'));
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
await useConfigStore.getState().loadModels();
const state = useConfigStore.getState();
expect(state.modelsError).toBe('Models error');
expect(state.modelsLoading).toBe(false);
expect(state.models).toEqual([]);
});
it('handles null result from client', async () => {
mockClient.listModels = vi.fn().mockResolvedValue({ models: null });
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.getState().setConfigStoreClient(mockClient);
await useConfigStore.getState().loadModels();
expect(useConfigStore.getState().models).toEqual([]);
});
});
describe('clearError', () => {
it('clears the error state', async () => {
const { useConfigStore } = await import('../../../desktop/src/store/configStore');
useConfigStore.setState({ error: 'Some error' });
useConfigStore.getState().clearError();
expect(useConfigStore.getState().error).toBeNull();
});
});
});