197 lines
6.1 KiB
TypeScript
197 lines
6.1 KiB
TypeScript
import React from 'react';
|
|
import { describe, expect, it, beforeEach, vi } from 'vitest';
|
|
import { render } from '@testing-library/react';
|
|
import type { A2UIClientAction } from '../../../src/main/a2ui/types';
|
|
import { useAppStore } from '../../../src/renderer/store';
|
|
|
|
// Capture the onSurfaceAction prop passed to ChatTranscript
|
|
let capturedOnSurfaceAction: ((action: A2UIClientAction) => void) | undefined;
|
|
|
|
vi.mock('../../../src/renderer/components/ChatSurface', () => ({
|
|
ChatTranscript: (props: { onSurfaceAction?: (action: A2UIClientAction) => void }) => {
|
|
capturedOnSurfaceAction = props.onSurfaceAction;
|
|
return React.createElement('div', { 'data-testid': 'mock-chat-transcript' });
|
|
},
|
|
}));
|
|
|
|
vi.mock('../../../src/renderer/navigation/useChatSurfaceState', () => ({
|
|
useChatSurfaceState: () => ({
|
|
messages: [{ role: 'user', content: 'test' }],
|
|
isStreaming: false,
|
|
streamingContent: '',
|
|
toolEvents: [],
|
|
beginUserTurn: vi.fn(),
|
|
appendStreamDelta: vi.fn(),
|
|
recordToolCall: vi.fn(),
|
|
recordToolResult: vi.fn(),
|
|
finalizeAssistantTurn: vi.fn(),
|
|
appendAssistantMessage: vi.fn(),
|
|
stopStreaming: vi.fn(),
|
|
abortStreaming: vi.fn(),
|
|
getStreamingContent: vi.fn(() => ''),
|
|
setMessages: vi.fn(),
|
|
}),
|
|
}));
|
|
|
|
vi.mock('../../../src/renderer/navigation/useChatMessageSender', () => ({
|
|
useChatMessageSender: () => ({
|
|
sendMessage: vi.fn(),
|
|
}),
|
|
}));
|
|
|
|
vi.mock('../../../src/renderer/a2ui/useA2UISurface', () => ({
|
|
useA2UISurface: () => ({
|
|
surfaces: [],
|
|
surfacesByTurn: new Map(),
|
|
latestSurfaceId: null,
|
|
dismissedSurfaceIds: new Set(),
|
|
dismissSurface: vi.fn(),
|
|
updateLocalData: vi.fn(),
|
|
getDataModel: vi.fn(() => ({})),
|
|
clearSurfaces: vi.fn(),
|
|
replayFromMessages: vi.fn(),
|
|
}),
|
|
}));
|
|
|
|
function setupChatApi() {
|
|
window.electronAPI.chat = {
|
|
checkReady: vi.fn(),
|
|
validateApiKey: vi.fn(),
|
|
setApiKey: vi.fn(),
|
|
getApiKey: vi.fn(),
|
|
getAvailableModels: vi.fn(),
|
|
setDefaultModel: vi.fn(),
|
|
getSystemPrompt: vi.fn(),
|
|
setSystemPrompt: vi.fn(),
|
|
getConversations: vi.fn(),
|
|
createConversation: vi.fn(),
|
|
getConversation: vi.fn().mockResolvedValue({ id: 'conv-1', title: 'Test', model: 'm' }),
|
|
updateConversation: vi.fn(),
|
|
deleteConversation: vi.fn(),
|
|
sendMessage: vi.fn(),
|
|
addSystemEvent: vi.fn(),
|
|
abortMessage: vi.fn(),
|
|
getHistory: vi.fn().mockResolvedValue([]),
|
|
clearMessages: vi.fn(),
|
|
setConversationModel: vi.fn(),
|
|
analyzeTaxonomy: vi.fn(),
|
|
analyzeMediaImage: vi.fn(),
|
|
onStreamDelta: vi.fn(() => vi.fn()),
|
|
onToolCall: vi.fn(() => vi.fn()),
|
|
onToolResult: vi.fn(() => vi.fn()),
|
|
onTitleUpdated: vi.fn(() => vi.fn()),
|
|
onA2UIMessage: vi.fn(() => vi.fn()),
|
|
onTokenUsage: vi.fn(() => vi.fn()),
|
|
dispatchA2UIAction: vi.fn(),
|
|
validateMistralApiKey: vi.fn().mockResolvedValue({ isValid: false, models: [] }),
|
|
setMistralApiKey: vi.fn().mockResolvedValue({ success: true }),
|
|
getMistralApiKey: vi.fn().mockResolvedValue({ hasKey: false, maskedKey: '' }),
|
|
getTitleModel: vi.fn().mockResolvedValue({ success: true, modelId: 'claude-haiku-4-5' }),
|
|
setTitleModel: vi.fn().mockResolvedValue({ success: true }),
|
|
getImageAnalysisModel: vi.fn().mockResolvedValue({ success: true, modelId: 'claude-sonnet-4-5' }),
|
|
setImageAnalysisModel: vi.fn().mockResolvedValue({ success: true }),
|
|
} as never;
|
|
}
|
|
|
|
describe('A2UI surface action wiring', () => {
|
|
beforeEach(() => {
|
|
capturedOnSurfaceAction = undefined;
|
|
setupChatApi();
|
|
});
|
|
|
|
describe('AssistantSidebar', () => {
|
|
it('routes surface actions through dispatchAssistantAction, not IPC', async () => {
|
|
const { AssistantSidebar } = await import(
|
|
'../../../src/renderer/components/AssistantSidebar/AssistantSidebar'
|
|
);
|
|
|
|
const setSelectedPost = vi.fn();
|
|
const openTab = vi.fn();
|
|
const setActiveView = vi.fn();
|
|
|
|
useAppStore.setState({
|
|
tabs: [],
|
|
activeTabId: null,
|
|
posts: [],
|
|
media: [],
|
|
setSelectedPost,
|
|
setSelectedMedia: vi.fn(),
|
|
openTab,
|
|
setActiveView,
|
|
toggleSidebar: vi.fn(),
|
|
togglePanel: vi.fn(),
|
|
toggleAssistantSidebar: vi.fn(),
|
|
});
|
|
|
|
render(React.createElement(AssistantSidebar));
|
|
|
|
expect(capturedOnSurfaceAction).toBeDefined();
|
|
|
|
capturedOnSurfaceAction!({
|
|
surfaceId: 'surface-1',
|
|
componentId: 'card-1',
|
|
action: 'openPost',
|
|
payload: { postId: 'post-abc' },
|
|
});
|
|
|
|
expect(setActiveView).toHaveBeenCalledWith('posts');
|
|
expect(setSelectedPost).toHaveBeenCalledWith('post-abc');
|
|
expect(openTab).toHaveBeenCalledWith({
|
|
type: 'post',
|
|
id: 'post-abc',
|
|
isTransient: false,
|
|
});
|
|
|
|
// The IPC no-op handler must NOT be called
|
|
expect(window.electronAPI.chat.dispatchA2UIAction).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('ChatPanel', () => {
|
|
it('routes surface actions through dispatchAssistantAction, not IPC', async () => {
|
|
const { ChatPanel } = await import(
|
|
'../../../src/renderer/components/ChatPanel/ChatPanel'
|
|
);
|
|
|
|
const setSelectedPost = vi.fn();
|
|
const openTab = vi.fn();
|
|
const setActiveView = vi.fn();
|
|
|
|
useAppStore.setState({
|
|
tabs: [],
|
|
activeTabId: null,
|
|
posts: [],
|
|
media: [],
|
|
setSelectedPost,
|
|
setSelectedMedia: vi.fn(),
|
|
openTab,
|
|
setActiveView,
|
|
toggleSidebar: vi.fn(),
|
|
togglePanel: vi.fn(),
|
|
toggleAssistantSidebar: vi.fn(),
|
|
});
|
|
|
|
render(React.createElement(ChatPanel, { conversationId: 'conv-1' }));
|
|
|
|
expect(capturedOnSurfaceAction).toBeDefined();
|
|
|
|
capturedOnSurfaceAction!({
|
|
surfaceId: 'surface-2',
|
|
componentId: 'card-2',
|
|
action: 'openPost',
|
|
payload: { postId: 'post-xyz' },
|
|
});
|
|
|
|
expect(setActiveView).toHaveBeenCalledWith('posts');
|
|
expect(setSelectedPost).toHaveBeenCalledWith('post-xyz');
|
|
expect(openTab).toHaveBeenCalledWith({
|
|
type: 'post',
|
|
id: 'post-xyz',
|
|
isTransient: false,
|
|
});
|
|
|
|
expect(window.electronAPI.chat.dispatchA2UIAction).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|