Files
NianToB/tests/unit/chat-input.test.tsx

136 lines
3.9 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
import { fireEvent, render, screen } from '@testing-library/react';
import { ChatInput } from '@/pages/Chat/ChatInput';
const { agentsState, chatState, gatewayState } = vi.hoisted(() => ({
agentsState: {
agents: [] as Array<Record<string, unknown>>,
},
chatState: {
currentAgentId: 'main',
},
gatewayState: {
status: { state: 'running', port: 18789 },
},
}));
vi.mock('@/stores/agents', () => ({
useAgentsStore: (selector: (state: typeof agentsState) => unknown) => selector(agentsState),
}));
vi.mock('@/stores/chat', () => ({
useChatStore: (selector: (state: typeof chatState) => unknown) => selector(chatState),
}));
vi.mock('@/stores/gateway', () => ({
useGatewayStore: (selector: (state: typeof gatewayState) => unknown) => selector(gatewayState),
}));
vi.mock('@/lib/host-api', () => ({
hostApiFetch: vi.fn(),
}));
vi.mock('@/lib/api-client', () => ({
invokeIpc: vi.fn(),
}));
function translate(key: string, vars?: Record<string, unknown>): string {
switch (key) {
case 'composer.attachFiles':
return 'Attach files';
case 'composer.pickAgent':
return 'Choose agent';
case 'composer.clearTarget':
return 'Clear target agent';
case 'composer.targetChip':
return `@${String(vars?.agent ?? '')}`;
case 'composer.agentPickerTitle':
return 'Route the next message to another agent';
case 'composer.gatewayDisconnectedPlaceholder':
return 'Gateway not connected...';
case 'composer.send':
return 'Send';
case 'composer.stop':
return 'Stop';
case 'composer.gatewayConnected':
return 'connected';
case 'composer.gatewayStatus':
return `gateway ${String(vars?.state ?? '')} | port: ${String(vars?.port ?? '')} ${String(vars?.pid ?? '')}`.trim();
case 'composer.retryFailedAttachments':
return 'Retry failed attachments';
default:
return key;
}
}
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: translate,
}),
}));
describe('ChatInput context controls', () => {
beforeEach(() => {
agentsState.agents = [];
chatState.currentAgentId = 'main';
gatewayState.status = { state: 'running', port: 18789 };
});
it('does not expose the old @agent picker', () => {
agentsState.agents = [
{
id: 'main',
name: 'Main',
isDefault: true,
modelDisplay: 'MiniMax',
inheritedModel: true,
workspace: '~/.openclaw/workspace',
agentDir: '~/.openclaw/agents/main/agent',
mainSessionKey: 'agent:main:main',
channelTypes: [],
},
];
render(<ChatInput onSend={vi.fn()} />);
expect(screen.queryByTitle('Choose agent')).not.toBeInTheDocument();
});
it('passes selected knowledge documents as context option', () => {
const onSend = vi.fn();
render(
<ChatInput
onSend={onSend}
knowledgeDocuments={[
{
id: 'doc-1',
name: 'handbook.docx',
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
storedPath: '/kb/files/handbook.docx',
textPath: '/kb/texts/handbook.txt',
},
{
id: 'doc-2',
name: 'faq.md',
mimeType: 'text/markdown',
storedPath: '/kb/files/faq.md',
},
]}
workspaceId="workspace-1"
/>,
);
fireEvent.click(screen.getByTestId('chat-composer-knowledge-button'));
fireEvent.click(screen.getByText('handbook.docx'));
fireEvent.click(screen.getByText('faq.md'));
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'Use company docs' } });
fireEvent.click(screen.getByTitle('Send'));
expect(onSend).toHaveBeenCalledWith('Use company docs', undefined, null, {
useKnowledgeBase: true,
workspaceId: 'workspace-1',
selectedKnowledgeDocumentIds: ['doc-1', 'doc-2'],
});
});
});