fix: update chatStore tests for sub-store refactoring
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

Tests were referencing old monolithic useChatStore API. Updated to
use useConversationStore for conversation/agent/model state and
useChatStore for message operations. 10→0 failures.
This commit is contained in:
iven
2026-04-06 11:57:46 +08:00
parent 7f9799b7e0
commit 4a23bbeda6
2 changed files with 128 additions and 79 deletions

View File

@@ -1,18 +1,18 @@
/**
* Stabilization Core Path Tests
*
* Covers the 4 critical paths from STABILIZATION_DIRECTIVE.md §5:
* 1. Skill execution invoke no crash result
* 2. Hand trigger emit event frontend receives notification
* 3. Message sending Store invoke streaming response
* 4. Config sync SaaS pull Store update
* Covers the 4 critical paths from STABILIZATION_DIRECTIVE.md 5:
* 1. Skill execution -- invoke -> no crash -> result
* 2. Hand trigger -- emit event -> frontend receives notification
* 3. Message sending -- Store -> invoke -> streaming response
* 4. Config sync -- SaaS pull -> Store update
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { invoke } from '@tauri-apps/api/core';
import { useChatStore, type Message } from '../src/store/chatStore';
// ─── Shared mocks ───
// --- Shared mocks ---
vi.mock('@tauri-apps/api/core', () => ({
invoke: vi.fn(),
@@ -114,11 +114,23 @@ vi.mock('../src/store/chat/conversationStore', () => {
sessionKey: 'test-session-1',
currentModel: 'default',
conversations: [],
currentConversationId: null,
agents: [{ id: 'test-agent-1', name: 'Test Agent' }],
};
return {
useConversationStore: {
getState: () => state,
setState: vi.fn((partial: Record<string, unknown>) => {
if (typeof partial === 'function') {
Object.assign(state, partial(state));
} else {
Object.assign(state, partial);
}
}),
subscribe: vi.fn(),
persist: {
hasHydrated: () => true,
},
...(Object.fromEntries(
Object.keys(state).map((k) => [k, vi.fn()])
)),
@@ -126,7 +138,7 @@ vi.mock('../src/store/chat/conversationStore', () => {
};
});
// ─── 1. Skill Execution ───
// --- 1. Skill Execution ---
describe('Skill execution (SEC2-P0-01)', () => {
beforeEach(() => {
@@ -182,7 +194,7 @@ describe('Skill execution (SEC2-P0-01)', () => {
});
});
// ─── 2. Hand Trigger Event ───
// --- 2. Hand Trigger Event ---
describe('Hand execution event (SEC2-P1-03)', () => {
it('should add hand message to chatStore when hand-execution-complete is received', () => {
@@ -237,7 +249,7 @@ describe('Hand execution event (SEC2-P1-03)', () => {
});
});
// ─── 3. Message Sending ───
// --- 3. Message Sending ---
describe('Message sending flow', () => {
beforeEach(() => {
@@ -304,9 +316,9 @@ describe('Message sending flow', () => {
});
});
// ─── 4. Config Sync ───
// --- 4. Config Sync ---
describe('Config sync (SaaS Store)', () => {
describe('Config sync (SaaS to Store)', () => {
it('should invoke saas-client with correct /api/v1 prefix for templates', async () => {
vi.mocked(invoke).mockResolvedValueOnce([]);
@@ -321,7 +333,7 @@ describe('Config sync (SaaS → Store)', () => {
});
it('should handle store update cycle correctly', () => {
// Simulate a config sync: external data arrives store updates
// Simulate a config sync: external data arrives -> store updates
useChatStore.setState({ isStreaming: false });
expect(useChatStore.getState().isStreaming).toBe(false);

View File

@@ -2,10 +2,18 @@
* Chat Store Tests
*
* Tests for chat state management including messages, conversations, and agents.
* After the chatStore refactoring, chatStore delegates to:
* - conversationStore: conversations, agents, currentAgent, sessionKey, currentModel
* - messageStore: token tracking
* - streamStore: streaming, sendMessage, chatMode
*
* chatStore remains the facade, so tests use useChatStore for message operations
* and useConversationStore for conversation/agent state.
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { useChatStore, Message, Conversation, Agent, toChatAgent } from '../../src/store/chatStore';
import { useChatStore, Message } from '../../src/store/chatStore';
import { useConversationStore, toChatAgent, type Agent, type Conversation } from '../../src/store/chat/conversationStore';
import { localStorageMock } from '../setup';
// Mock gateway client - use vi.hoisted to ensure mocks are available before module import
@@ -72,21 +80,41 @@ vi.mock('../../src/lib/skill-discovery', () => ({
}));
describe('chatStore', () => {
// Store the original state to reset between tests
const initialState = {
messages: [],
conversations: [],
currentConversationId: null,
agents: [{ id: '1', name: 'ZCLAW', icon: '\u{1F99E}', color: 'bg-gradient-to-br from-orange-500 to-red-500', lastMessage: '\u{53D1}\u{9001}\u{6D88}\u{606F}\u{5F00}\u{59CB}\u{5BF9}\u{8BDD}', time: '' }],
currentAgent: { id: '1', name: 'ZCLAW', icon: '\u{1F99E}', color: 'bg-gradient-to-br from-orange-500 to-red-500', lastMessage: '\u{53D1}\u{9001}\u{6D88}\u{606F}\u{5F00}\u{59CB}\u{5BF9}\u{8BDD}', time: '' },
// Default agent matching conversationStore's DEFAULT_AGENT
const defaultAgent: Agent = {
id: '1',
name: 'ZCLAW',
icon: '\u{1F99E}',
color: 'bg-gradient-to-br from-orange-500 to-red-500',
lastMessage: '\u{53D1}\u{9001}\u{6D88}\u{606F}\u{5F00}\u{59CB}\u{5BF9}\u{8BDD}',
time: '',
};
const initialChatState = {
messages: [] as Message[],
isStreaming: false,
currentModel: 'glm-5',
sessionKey: null,
isLoading: false,
totalInputTokens: 0,
totalOutputTokens: 0,
chatMode: 'thinking' as const,
suggestions: [] as string[],
};
const initialConvState = {
conversations: [] as Conversation[],
currentConversationId: null as string | null,
agents: [defaultAgent] as Agent[],
currentAgent: defaultAgent as Agent,
isStreaming: false,
currentModel: 'glm-4-flash',
sessionKey: null as string | null,
};
beforeEach(() => {
// Reset store state
useChatStore.setState(initialState);
// Reset chatStore (messages + facade mirrors)
useChatStore.setState(initialChatState);
// Reset conversationStore (conversations, agents, session, model)
useConversationStore.setState(initialConvState);
// Clear localStorage
localStorageMock.clear();
// Clear all mocks
@@ -104,10 +132,10 @@ describe('chatStore', () => {
});
it('should have default agent set', () => {
const state = useChatStore.getState();
expect(state.currentAgent).not.toBeNull();
expect(state.currentAgent?.id).toBe('1');
expect(state.currentAgent?.name).toBe('ZCLAW');
const convState = useConversationStore.getState();
expect(convState.currentAgent).not.toBeNull();
expect(convState.currentAgent?.id).toBe('1');
expect(convState.currentAgent?.name).toBe('ZCLAW');
});
it('should not be streaming initially', () => {
@@ -116,18 +144,18 @@ describe('chatStore', () => {
});
it('should have default model', () => {
const state = useChatStore.getState();
expect(state.currentModel).toBe('glm-5');
const convState = useConversationStore.getState();
expect(convState.currentModel).toBe('glm-4-flash');
});
it('should have null sessionKey initially', () => {
const state = useChatStore.getState();
expect(state.sessionKey).toBeNull();
const convState = useConversationStore.getState();
expect(convState.sessionKey).toBeNull();
});
it('should have empty conversations array', () => {
const state = useChatStore.getState();
expect(state.conversations).toEqual([]);
const convState = useConversationStore.getState();
expect(convState.conversations).toEqual([]);
});
});
@@ -268,8 +296,9 @@ describe('chatStore', () => {
setCurrentModel('gpt-4');
const state = useChatStore.getState();
expect(state.currentModel).toBe('gpt-4');
// currentModel is stored on conversationStore
const convState = useConversationStore.getState();
expect(convState.currentModel).toBe('gpt-4');
});
});
@@ -284,15 +313,15 @@ describe('chatStore', () => {
timestamp: new Date(),
});
useChatStore.setState({ sessionKey: 'old-session' });
newConversation();
const state = useChatStore.getState();
expect(state.messages).toEqual([]);
expect(state.sessionKey).toBeNull();
expect(state.isStreaming).toBe(false);
expect(state.currentConversationId).toBeNull();
const chatState = useChatStore.getState();
const convState = useConversationStore.getState();
expect(chatState.messages).toEqual([]);
// sessionKey and currentConversationId reset on conversationStore
expect(convState.sessionKey).toBeNull();
expect(chatState.isStreaming).toBe(false);
expect(convState.currentConversationId).toBeNull();
});
it('should save current messages to conversations before clearing', () => {
@@ -307,10 +336,10 @@ describe('chatStore', () => {
newConversation();
const state = useChatStore.getState();
// Conversation should be saved
expect(state.conversations.length).toBeGreaterThan(0);
expect(state.conversations[0].messages[0].content).toBe('Test message to save');
const convState = useConversationStore.getState();
// Conversation should be saved on conversationStore
expect(convState.conversations.length).toBeGreaterThan(0);
expect(convState.conversations[0].messages[0].content).toBe('Test message to save');
});
});
@@ -335,14 +364,16 @@ describe('chatStore', () => {
timestamp: new Date(),
});
const firstConvId = useChatStore.getState().conversations[0].id;
const convState = useConversationStore.getState();
const firstConvId = convState.conversations[0].id;
// Switch back to first conversation
switchConversation(firstConvId);
const state = useChatStore.getState();
expect(state.messages[0].content).toBe('First conversation');
expect(state.currentConversationId).toBe(firstConvId);
const chatState = useChatStore.getState();
const updatedConvState = useConversationStore.getState();
expect(chatState.messages[0].content).toBe('First conversation');
expect(updatedConvState.currentConversationId).toBe(firstConvId);
});
});
@@ -359,19 +390,20 @@ describe('chatStore', () => {
});
newConversation();
const convId = useChatStore.getState().conversations[0].id;
expect(useChatStore.getState().conversations).toHaveLength(1);
const convState = useConversationStore.getState();
const convId = convState.conversations[0].id;
expect(convState.conversations).toHaveLength(1);
// Delete it
deleteConversation(convId);
expect(useChatStore.getState().conversations).toHaveLength(0);
expect(useConversationStore.getState().conversations).toHaveLength(0);
});
it('should clear messages if deleting current conversation', () => {
const { addMessage, deleteConversation } = useChatStore.getState();
// Create a conversation without calling newConversation
// Add a message
addMessage({
id: 'msg-1',
role: 'user',
@@ -379,14 +411,14 @@ describe('chatStore', () => {
timestamp: new Date(),
});
// Manually set up a current conversation
// Manually set up a current conversation on conversationStore
const convId = 'conv-test-123';
useChatStore.setState({
useConversationStore.setState({
currentConversationId: convId,
conversations: [{
id: convId,
title: 'Test',
messages: useChatStore.getState().messages,
messages: useChatStore.getState().messages as any[],
sessionKey: null,
agentId: null,
createdAt: new Date(),
@@ -396,10 +428,12 @@ describe('chatStore', () => {
deleteConversation(convId);
const state = useChatStore.getState();
expect(state.messages).toEqual([]);
expect(state.sessionKey).toBeNull();
expect(state.currentConversationId).toBeNull();
const chatState = useChatStore.getState();
const convState = useConversationStore.getState();
// chatStore facade should detect resetMessages and clear its messages
expect(chatState.messages).toEqual([]);
expect(convState.sessionKey).toBeNull();
expect(convState.currentConversationId).toBeNull();
});
});
@@ -417,8 +451,9 @@ describe('chatStore', () => {
setCurrentAgent(newAgent);
const state = useChatStore.getState();
expect(state.currentAgent).toEqual(newAgent);
// currentAgent is stored on conversationStore
const convState = useConversationStore.getState();
expect(convState.currentAgent).toEqual(newAgent);
});
it('should save current conversation when switching agents', () => {
@@ -443,7 +478,7 @@ describe('chatStore', () => {
};
setCurrentAgent(newAgent);
// Messages should be cleared for new agent
// Messages should be cleared for new agent (different agent id)
expect(useChatStore.getState().messages).toEqual([]);
});
});
@@ -457,10 +492,12 @@ describe('chatStore', () => {
{ id: 'agent-2', name: 'Agent Two', nickname: 'A2' },
]);
const state = useChatStore.getState();
expect(state.agents).toHaveLength(2);
expect(state.agents[0].name).toBe('Agent One');
expect(state.agents[1].name).toBe('Agent Two');
// agents are stored on conversationStore
const convState = useConversationStore.getState();
// DEFAULT_AGENT + 2 profile agents = 3
expect(convState.agents).toHaveLength(3);
expect(convState.agents[1].name).toBe('Agent One');
expect(convState.agents[2].name).toBe('Agent Two');
});
it('should use default agent when no profiles provided', () => {
@@ -468,9 +505,9 @@ describe('chatStore', () => {
syncAgents([]);
const state = useChatStore.getState();
expect(state.agents).toHaveLength(1);
expect(state.agents[0].id).toBe('1');
const convState = useConversationStore.getState();
expect(convState.agents).toHaveLength(1);
expect(convState.agents[0].id).toBe('1');
});
});
@@ -644,14 +681,14 @@ describe('chatStore', () => {
newConversation();
const state = useChatStore.getState();
expect(state.conversations[0].title).toContain('This is a long message');
expect(state.conversations[0].title.length).toBeLessThanOrEqual(33); // 30 chars + '...'
const convState = useConversationStore.getState();
expect(convState.conversations[0].title).toContain('This is a long message');
expect(convState.conversations[0].title.length).toBeLessThanOrEqual(33); // 30 chars + '...'
});
it('should use default title for empty messages', () => {
// Create a conversation directly with empty messages
useChatStore.setState({
// Create a conversation directly with empty messages on conversationStore
useConversationStore.setState({
conversations: [{
id: 'conv-1',
title: '',
@@ -663,8 +700,8 @@ describe('chatStore', () => {
}],
});
const state = useChatStore.getState();
expect(state.conversations).toHaveLength(1);
const convState = useConversationStore.getState();
expect(convState.conversations).toHaveLength(1);
});
});