- 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>
252 lines
8.3 KiB
TypeScript
252 lines
8.3 KiB
TypeScript
/**
|
|
* Tests for notification middleware
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
import { notificationMiddleware } from './notification.js';
|
|
import { useSessionStore } from '../../stores/sessionStore.js';
|
|
import { useNotificationStore } from '../../stores/notificationStore.js';
|
|
import type { ClientEvent } from '../types.js';
|
|
|
|
describe('notificationMiddleware', () => {
|
|
// Mock next function
|
|
const next = vi.fn();
|
|
|
|
beforeEach(() => {
|
|
// Reset stores
|
|
useSessionStore.setState({
|
|
currentSessionId: 'current-session',
|
|
isWelcomeState: false,
|
|
isCreatingSession: false,
|
|
isSwitchingSession: false,
|
|
isReplayingHistory: false,
|
|
isLoadingHistory: false,
|
|
});
|
|
|
|
useNotificationStore.setState({
|
|
toasts: [],
|
|
maxToasts: 5,
|
|
});
|
|
|
|
// Clear mock
|
|
next.mockClear();
|
|
});
|
|
|
|
it('should always call next', () => {
|
|
const event: ClientEvent = {
|
|
name: 'llm:thinking',
|
|
sessionId: 'test-session',
|
|
};
|
|
|
|
notificationMiddleware(event, next);
|
|
|
|
expect(next).toHaveBeenCalledWith(event);
|
|
expect(next).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
describe('notification suppression', () => {
|
|
it('should suppress notifications during history replay', () => {
|
|
useSessionStore.setState({ isReplayingHistory: true });
|
|
|
|
const event: ClientEvent = {
|
|
name: 'llm:error',
|
|
error: new Error('Test error'),
|
|
sessionId: 'test-session',
|
|
};
|
|
|
|
notificationMiddleware(event, next);
|
|
|
|
expect(next).toHaveBeenCalled();
|
|
expect(useNotificationStore.getState().toasts).toHaveLength(0);
|
|
});
|
|
|
|
it('should suppress notifications during session switch', () => {
|
|
useSessionStore.setState({ isSwitchingSession: true });
|
|
|
|
const event: ClientEvent = {
|
|
name: 'llm:error',
|
|
error: new Error('Test error'),
|
|
sessionId: 'test-session',
|
|
};
|
|
|
|
notificationMiddleware(event, next);
|
|
|
|
expect(next).toHaveBeenCalled();
|
|
expect(useNotificationStore.getState().toasts).toHaveLength(0);
|
|
});
|
|
|
|
it('should suppress notifications during history loading', () => {
|
|
useSessionStore.setState({ isLoadingHistory: true });
|
|
|
|
const event: ClientEvent = {
|
|
name: 'llm:response',
|
|
content: 'Test response',
|
|
sessionId: 'background-session',
|
|
};
|
|
|
|
notificationMiddleware(event, next);
|
|
|
|
expect(next).toHaveBeenCalled();
|
|
expect(useNotificationStore.getState().toasts).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
describe('llm:error events', () => {
|
|
it('should NOT create toast for errors in current session (shown inline)', () => {
|
|
useSessionStore.setState({ currentSessionId: 'current-session' });
|
|
|
|
const event: ClientEvent = {
|
|
name: 'llm:error',
|
|
error: new Error('Test error message'),
|
|
sessionId: 'current-session',
|
|
};
|
|
|
|
notificationMiddleware(event, next);
|
|
|
|
// Errors in current session are shown inline via ErrorBanner, not as toasts
|
|
const { toasts } = useNotificationStore.getState();
|
|
expect(toasts).toHaveLength(0);
|
|
});
|
|
|
|
it('should create toast for errors in background session', () => {
|
|
useSessionStore.setState({ currentSessionId: 'current-session' });
|
|
|
|
const event: ClientEvent = {
|
|
name: 'llm:error',
|
|
error: new Error('Test error'),
|
|
sessionId: 'background-session',
|
|
};
|
|
|
|
notificationMiddleware(event, next);
|
|
|
|
const { toasts } = useNotificationStore.getState();
|
|
expect(toasts).toHaveLength(1);
|
|
expect(toasts[0].title).toBe('Error in background session');
|
|
expect(toasts[0].description).toBe('Test error');
|
|
expect(toasts[0].intent).toBe('danger');
|
|
expect(toasts[0].sessionId).toBe('background-session');
|
|
});
|
|
|
|
it('should handle error without message in background session', () => {
|
|
useSessionStore.setState({ currentSessionId: 'current-session' });
|
|
|
|
const event: ClientEvent = {
|
|
name: 'llm:error',
|
|
error: new Error(),
|
|
sessionId: 'background-session',
|
|
};
|
|
|
|
notificationMiddleware(event, next);
|
|
|
|
const { toasts } = useNotificationStore.getState();
|
|
expect(toasts).toHaveLength(1);
|
|
expect(toasts[0].description).toBe('An error occurred');
|
|
});
|
|
});
|
|
|
|
describe('llm:response events', () => {
|
|
it('should NOT create toast for responses in current session', () => {
|
|
useSessionStore.setState({ currentSessionId: 'current-session' });
|
|
|
|
const event: ClientEvent = {
|
|
name: 'llm:response',
|
|
content: 'Test response',
|
|
sessionId: 'current-session',
|
|
};
|
|
|
|
notificationMiddleware(event, next);
|
|
|
|
const { toasts } = useNotificationStore.getState();
|
|
expect(toasts).toHaveLength(0);
|
|
});
|
|
|
|
it('should create toast for responses in background session', () => {
|
|
useSessionStore.setState({ currentSessionId: 'current-session' });
|
|
|
|
const event: ClientEvent = {
|
|
name: 'llm:response',
|
|
content: 'Test response',
|
|
sessionId: 'background-session',
|
|
};
|
|
|
|
notificationMiddleware(event, next);
|
|
|
|
const { toasts } = useNotificationStore.getState();
|
|
expect(toasts).toHaveLength(1);
|
|
expect(toasts[0].title).toBe('Response Ready');
|
|
expect(toasts[0].description).toBe('Agent completed in background session');
|
|
expect(toasts[0].intent).toBe('info');
|
|
expect(toasts[0].sessionId).toBe('background-session');
|
|
});
|
|
|
|
it('should create toast when no session is active (treated as background)', () => {
|
|
useSessionStore.setState({ currentSessionId: null });
|
|
|
|
const event: ClientEvent = {
|
|
name: 'llm:response',
|
|
content: 'Test response',
|
|
sessionId: 'some-session',
|
|
};
|
|
|
|
notificationMiddleware(event, next);
|
|
|
|
const { toasts } = useNotificationStore.getState();
|
|
// When no session is active, any session is considered "background"
|
|
expect(toasts).toHaveLength(1);
|
|
expect(toasts[0].sessionId).toBe('some-session');
|
|
});
|
|
});
|
|
|
|
describe('other events', () => {
|
|
it('should not create toast for llm:thinking', () => {
|
|
const event: ClientEvent = {
|
|
name: 'llm:thinking',
|
|
sessionId: 'test-session',
|
|
};
|
|
|
|
notificationMiddleware(event, next);
|
|
|
|
expect(useNotificationStore.getState().toasts).toHaveLength(0);
|
|
});
|
|
|
|
it('should not create toast for llm:chunk', () => {
|
|
const event: ClientEvent = {
|
|
name: 'llm:chunk',
|
|
chunkType: 'text',
|
|
content: 'Test chunk',
|
|
sessionId: 'test-session',
|
|
};
|
|
|
|
notificationMiddleware(event, next);
|
|
|
|
expect(useNotificationStore.getState().toasts).toHaveLength(0);
|
|
});
|
|
|
|
it('should not create toast for llm:tool-call', () => {
|
|
const event: ClientEvent = {
|
|
name: 'llm:tool-call',
|
|
toolName: 'test-tool',
|
|
args: {},
|
|
callId: 'call-123',
|
|
sessionId: 'test-session',
|
|
};
|
|
|
|
notificationMiddleware(event, next);
|
|
|
|
expect(useNotificationStore.getState().toasts).toHaveLength(0);
|
|
});
|
|
|
|
it('should not create toast for connection:status', () => {
|
|
const event: ClientEvent = {
|
|
name: 'connection:status',
|
|
status: 'connected',
|
|
timestamp: Date.now(),
|
|
};
|
|
|
|
notificationMiddleware(event, next);
|
|
|
|
expect(useNotificationStore.getState().toasts).toHaveLength(0);
|
|
});
|
|
});
|
|
});
|