- Add intelligent-router.sh hook for automatic agent routing - Add AUTO-TRIGGER-SUMMARY.md documentation - Add FINAL-INTEGRATION-SUMMARY.md documentation - Complete Prometheus integration (6 commands + 4 tools) - Complete Dexto integration (12 commands + 5 tools) - Enhanced Ralph with access to all agents - Fix /clawd command (removed disable-model-invocation) - Update hooks.json to v5 with intelligent routing - 291 total skills now available - All 21 commands with automatic routing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
339 lines
14 KiB
TypeScript
339 lines
14 KiB
TypeScript
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import { useChatStore, generateMessageId, type Message } from './chatStore.js';
|
|
|
|
// Helper to create a test message
|
|
function createTestMessage(overrides: Partial<Message> = {}): Message {
|
|
return {
|
|
id: generateMessageId(),
|
|
role: 'user',
|
|
content: 'Test message',
|
|
createdAt: Date.now(),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('chatStore', () => {
|
|
const sessionId = 'test-session';
|
|
|
|
beforeEach(() => {
|
|
// Reset store to default state
|
|
useChatStore.setState({ sessions: new Map() });
|
|
});
|
|
|
|
describe('generateMessageId', () => {
|
|
it('should generate unique IDs', () => {
|
|
const id1 = generateMessageId();
|
|
const id2 = generateMessageId();
|
|
expect(id1).not.toBe(id2);
|
|
});
|
|
|
|
it('should start with msg- prefix', () => {
|
|
const id = generateMessageId();
|
|
expect(id.startsWith('msg-')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('initSession', () => {
|
|
it('should initialize a session with default state', () => {
|
|
useChatStore.getState().initSession(sessionId);
|
|
|
|
const state = useChatStore.getState().getSessionState(sessionId);
|
|
expect(state.messages).toEqual([]);
|
|
expect(state.streamingMessage).toBeNull();
|
|
expect(state.processing).toBe(false);
|
|
expect(state.error).toBeNull();
|
|
expect(state.loadingHistory).toBe(false);
|
|
});
|
|
|
|
it('should not overwrite existing session', () => {
|
|
const message = createTestMessage();
|
|
useChatStore.getState().initSession(sessionId);
|
|
useChatStore.getState().addMessage(sessionId, message);
|
|
useChatStore.getState().initSession(sessionId);
|
|
|
|
const messages = useChatStore.getState().getMessages(sessionId);
|
|
expect(messages).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
describe('addMessage', () => {
|
|
it('should add a message to a session', () => {
|
|
const message = createTestMessage();
|
|
useChatStore.getState().addMessage(sessionId, message);
|
|
|
|
const messages = useChatStore.getState().getMessages(sessionId);
|
|
expect(messages).toHaveLength(1);
|
|
expect(messages[0]).toEqual(message);
|
|
});
|
|
|
|
it('should create session if not exists', () => {
|
|
const message = createTestMessage();
|
|
useChatStore.getState().addMessage('new-session', message);
|
|
|
|
const messages = useChatStore.getState().getMessages('new-session');
|
|
expect(messages).toHaveLength(1);
|
|
});
|
|
|
|
it('should append multiple messages in order', () => {
|
|
const msg1 = createTestMessage({ content: 'First' });
|
|
const msg2 = createTestMessage({ content: 'Second' });
|
|
|
|
useChatStore.getState().addMessage(sessionId, msg1);
|
|
useChatStore.getState().addMessage(sessionId, msg2);
|
|
|
|
const messages = useChatStore.getState().getMessages(sessionId);
|
|
expect(messages).toHaveLength(2);
|
|
expect(messages[0].content).toBe('First');
|
|
expect(messages[1].content).toBe('Second');
|
|
});
|
|
});
|
|
|
|
describe('updateMessage', () => {
|
|
it('should update an existing message', () => {
|
|
const message = createTestMessage({ content: 'Original' });
|
|
useChatStore.getState().addMessage(sessionId, message);
|
|
|
|
useChatStore.getState().updateMessage(sessionId, message.id, {
|
|
content: 'Updated',
|
|
});
|
|
|
|
const updated = useChatStore.getState().getMessage(sessionId, message.id);
|
|
expect(updated?.content).toBe('Updated');
|
|
});
|
|
|
|
it('should not modify state for non-existent message', () => {
|
|
const message = createTestMessage();
|
|
useChatStore.getState().addMessage(sessionId, message);
|
|
|
|
useChatStore.getState().updateMessage(sessionId, 'non-existent', {
|
|
content: 'Updated',
|
|
});
|
|
|
|
// State should be unchanged - original message still present
|
|
const messages = useChatStore.getState().getMessages(sessionId);
|
|
expect(messages).toHaveLength(1);
|
|
expect(messages[0].content).toBe(message.content);
|
|
});
|
|
|
|
it('should not modify state for non-existent session', () => {
|
|
useChatStore.getState().updateMessage('non-existent', 'msg-id', {
|
|
content: 'Updated',
|
|
});
|
|
|
|
expect(useChatStore.getState().sessions.has('non-existent')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('removeMessage', () => {
|
|
it('should remove a message from a session', () => {
|
|
const message = createTestMessage();
|
|
useChatStore.getState().addMessage(sessionId, message);
|
|
useChatStore.getState().removeMessage(sessionId, message.id);
|
|
|
|
const messages = useChatStore.getState().getMessages(sessionId);
|
|
expect(messages).toHaveLength(0);
|
|
});
|
|
|
|
it('should not affect other messages', () => {
|
|
const msg1 = createTestMessage({ content: 'Keep' });
|
|
const msg2 = createTestMessage({ content: 'Remove' });
|
|
|
|
useChatStore.getState().addMessage(sessionId, msg1);
|
|
useChatStore.getState().addMessage(sessionId, msg2);
|
|
useChatStore.getState().removeMessage(sessionId, msg2.id);
|
|
|
|
const messages = useChatStore.getState().getMessages(sessionId);
|
|
expect(messages).toHaveLength(1);
|
|
expect(messages[0].content).toBe('Keep');
|
|
});
|
|
});
|
|
|
|
describe('clearMessages', () => {
|
|
it('should clear all messages in a session', () => {
|
|
useChatStore.getState().addMessage(sessionId, createTestMessage());
|
|
useChatStore.getState().addMessage(sessionId, createTestMessage());
|
|
useChatStore.getState().clearMessages(sessionId);
|
|
|
|
const messages = useChatStore.getState().getMessages(sessionId);
|
|
expect(messages).toHaveLength(0);
|
|
});
|
|
|
|
it('should also clear streaming message', () => {
|
|
const streaming = createTestMessage({ role: 'assistant' });
|
|
useChatStore.getState().setStreamingMessage(sessionId, streaming);
|
|
useChatStore.getState().clearMessages(sessionId);
|
|
|
|
const state = useChatStore.getState().getSessionState(sessionId);
|
|
expect(state.streamingMessage).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('streaming message', () => {
|
|
it('should set streaming message', () => {
|
|
const message = createTestMessage({ role: 'assistant', content: '' });
|
|
useChatStore.getState().setStreamingMessage(sessionId, message);
|
|
|
|
const state = useChatStore.getState().getSessionState(sessionId);
|
|
expect(state.streamingMessage).toEqual(message);
|
|
});
|
|
|
|
it('should clear streaming message', () => {
|
|
const message = createTestMessage({ role: 'assistant' });
|
|
useChatStore.getState().setStreamingMessage(sessionId, message);
|
|
useChatStore.getState().setStreamingMessage(sessionId, null);
|
|
|
|
const state = useChatStore.getState().getSessionState(sessionId);
|
|
expect(state.streamingMessage).toBeNull();
|
|
});
|
|
|
|
it('should append text content to streaming message', () => {
|
|
const message = createTestMessage({ role: 'assistant', content: 'Hello' });
|
|
useChatStore.getState().setStreamingMessage(sessionId, message);
|
|
useChatStore.getState().appendToStreamingMessage(sessionId, ' World');
|
|
|
|
const state = useChatStore.getState().getSessionState(sessionId);
|
|
expect(state.streamingMessage?.content).toBe('Hello World');
|
|
});
|
|
|
|
it('should append reasoning content to streaming message', () => {
|
|
const message = createTestMessage({ role: 'assistant', content: '', reasoning: '' });
|
|
useChatStore.getState().setStreamingMessage(sessionId, message);
|
|
useChatStore.getState().appendToStreamingMessage(sessionId, 'Thinking...', 'reasoning');
|
|
|
|
const state = useChatStore.getState().getSessionState(sessionId);
|
|
expect(state.streamingMessage?.reasoning).toBe('Thinking...');
|
|
});
|
|
|
|
it('should not append if no streaming message', () => {
|
|
// Should not throw
|
|
useChatStore.getState().appendToStreamingMessage(sessionId, 'Test');
|
|
|
|
const state = useChatStore.getState().getSessionState(sessionId);
|
|
expect(state.streamingMessage).toBeNull();
|
|
});
|
|
|
|
it('should finalize streaming message', () => {
|
|
const message = createTestMessage({ role: 'assistant', content: 'Response' });
|
|
useChatStore.getState().setStreamingMessage(sessionId, message);
|
|
useChatStore.getState().finalizeStreamingMessage(sessionId, {
|
|
tokenUsage: { totalTokens: 100 },
|
|
});
|
|
|
|
const state = useChatStore.getState().getSessionState(sessionId);
|
|
expect(state.streamingMessage).toBeNull();
|
|
expect(state.messages).toHaveLength(1);
|
|
expect(state.messages[0].content).toBe('Response');
|
|
expect(state.messages[0].tokenUsage?.totalTokens).toBe(100);
|
|
});
|
|
|
|
it('should not finalize if no streaming message', () => {
|
|
useChatStore.getState().finalizeStreamingMessage(sessionId);
|
|
|
|
const state = useChatStore.getState().getSessionState(sessionId);
|
|
expect(state.messages).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
describe('state flags', () => {
|
|
it('should set processing flag', () => {
|
|
useChatStore.getState().setProcessing(sessionId, true);
|
|
expect(useChatStore.getState().getSessionState(sessionId).processing).toBe(true);
|
|
|
|
useChatStore.getState().setProcessing(sessionId, false);
|
|
expect(useChatStore.getState().getSessionState(sessionId).processing).toBe(false);
|
|
});
|
|
|
|
it('should set error state', () => {
|
|
const error = {
|
|
id: 'error-1',
|
|
message: 'Test error',
|
|
timestamp: Date.now(),
|
|
};
|
|
useChatStore.getState().setError(sessionId, error);
|
|
expect(useChatStore.getState().getSessionState(sessionId).error).toEqual(error);
|
|
|
|
useChatStore.getState().setError(sessionId, null);
|
|
expect(useChatStore.getState().getSessionState(sessionId).error).toBeNull();
|
|
});
|
|
|
|
it('should set loading history flag', () => {
|
|
useChatStore.getState().setLoadingHistory(sessionId, true);
|
|
expect(useChatStore.getState().getSessionState(sessionId).loadingHistory).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('removeSession', () => {
|
|
it('should remove a session completely', () => {
|
|
useChatStore.getState().addMessage(sessionId, createTestMessage());
|
|
useChatStore.getState().removeSession(sessionId);
|
|
|
|
expect(useChatStore.getState().sessions.has(sessionId)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('selectors', () => {
|
|
it('getSessionState should return default for unknown session', () => {
|
|
const state = useChatStore.getState().getSessionState('unknown');
|
|
expect(state.messages).toEqual([]);
|
|
expect(state.processing).toBe(false);
|
|
});
|
|
|
|
it('getMessage should find message by ID', () => {
|
|
const message = createTestMessage();
|
|
useChatStore.getState().addMessage(sessionId, message);
|
|
|
|
const found = useChatStore.getState().getMessage(sessionId, message.id);
|
|
expect(found).toEqual(message);
|
|
});
|
|
|
|
it('getMessage should return undefined for unknown ID', () => {
|
|
const found = useChatStore.getState().getMessage(sessionId, 'unknown');
|
|
expect(found).toBeUndefined();
|
|
});
|
|
|
|
it('getMessageByToolCallId should find tool message', () => {
|
|
const message = createTestMessage({
|
|
role: 'tool',
|
|
toolCallId: 'tool-call-123',
|
|
});
|
|
useChatStore.getState().addMessage(sessionId, message);
|
|
|
|
const found = useChatStore
|
|
.getState()
|
|
.getMessageByToolCallId(sessionId, 'tool-call-123');
|
|
expect(found).toEqual(message);
|
|
});
|
|
});
|
|
|
|
describe('session isolation', () => {
|
|
it('should keep sessions separate', () => {
|
|
const session1 = 'session-1';
|
|
const session2 = 'session-2';
|
|
|
|
useChatStore
|
|
.getState()
|
|
.addMessage(session1, createTestMessage({ content: 'Session 1' }));
|
|
useChatStore
|
|
.getState()
|
|
.addMessage(session2, createTestMessage({ content: 'Session 2' }));
|
|
|
|
expect(useChatStore.getState().getMessages(session1)).toHaveLength(1);
|
|
expect(useChatStore.getState().getMessages(session2)).toHaveLength(1);
|
|
expect(useChatStore.getState().getMessages(session1)[0].content).toBe('Session 1');
|
|
expect(useChatStore.getState().getMessages(session2)[0].content).toBe('Session 2');
|
|
});
|
|
|
|
it('should not affect other sessions when clearing', () => {
|
|
const session1 = 'session-1';
|
|
const session2 = 'session-2';
|
|
|
|
useChatStore.getState().addMessage(session1, createTestMessage());
|
|
useChatStore.getState().addMessage(session2, createTestMessage());
|
|
useChatStore.getState().clearMessages(session1);
|
|
|
|
expect(useChatStore.getState().getMessages(session1)).toHaveLength(0);
|
|
expect(useChatStore.getState().getMessages(session2)).toHaveLength(1);
|
|
});
|
|
});
|
|
});
|