104 lines
4.0 KiB
TypeScript
104 lines
4.0 KiB
TypeScript
import React from 'react';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { fireEvent, render } from '@testing-library/react';
|
|
import { AssistantSidebar } from '../../../src/renderer/components/AssistantSidebar/AssistantSidebar';
|
|
import { AssistantPanelControls } from '../../../src/renderer/components/AssistantPanelControls';
|
|
import { useAppStore } from '../../../src/renderer/store';
|
|
|
|
describe('assistant sidebar guard rails', () => {
|
|
beforeEach(() => {
|
|
useAppStore.setState({ tabs: [], activeTabId: null, activeView: 'posts' });
|
|
|
|
window.electronAPI.chat = {
|
|
checkReady: vi.fn().mockResolvedValue({ ready: true }),
|
|
validateApiKey: vi.fn(),
|
|
setApiKey: vi.fn(),
|
|
getApiKey: vi.fn(),
|
|
getProtocolHealth: vi.fn(),
|
|
getAvailableModels: vi.fn(),
|
|
setDefaultModel: vi.fn(),
|
|
getSystemPrompt: vi.fn(),
|
|
setSystemPrompt: vi.fn(),
|
|
getConversations: vi.fn(),
|
|
createConversation: vi.fn(),
|
|
getConversation: vi.fn(),
|
|
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()),
|
|
} as never;
|
|
});
|
|
|
|
it('keeps assistant sidebar self-contained and avoids opening chat tabs on mount', () => {
|
|
render(React.createElement(AssistantSidebar));
|
|
|
|
expect(useAppStore.getState().tabs.some((tab) => tab.type === 'chat')).toBe(false);
|
|
});
|
|
|
|
it('renders rich assistant panel widget branches at runtime', () => {
|
|
const onAction = vi.fn();
|
|
|
|
const { container } = render(
|
|
React.createElement(AssistantPanelControls, {
|
|
elements: [
|
|
{ type: 'chart', chartType: 'bar', title: 'Trend', series: [{ label: 'Jan', value: 10 }] },
|
|
{
|
|
type: 'form',
|
|
formId: 'f1',
|
|
submitLabel: 'Submit',
|
|
action: 'submitNeedsInput',
|
|
fields: [{ key: 'name', label: 'Name', inputType: 'text', required: true }],
|
|
},
|
|
{ type: 'datePicker', key: 'date', label: 'Date', submitLabel: 'Pick', action: 'submitNeedsInput' },
|
|
{ type: 'card', title: 'Card', body: 'Body', actions: [{ label: 'Open', action: 'openSettings' }] },
|
|
{ type: 'image', src: 'https://example.com/a.png', caption: 'Image', action: 'openSettings' },
|
|
{
|
|
type: 'tabs',
|
|
tabs: [{ id: 'tab-1', label: 'Tab 1', elements: [{ type: 'text', text: 'Inside tab' }] }],
|
|
},
|
|
{ type: 'input', key: 'query', label: 'Query', inputType: 'text', submitLabel: 'Run', action: 'openSettings' },
|
|
],
|
|
onAction,
|
|
}),
|
|
);
|
|
|
|
expect(container.querySelector('.assistant-panel-chart')).not.toBeNull();
|
|
expect(container.querySelector('.assistant-panel-form')).not.toBeNull();
|
|
expect(container.querySelector('.assistant-panel-card')).not.toBeNull();
|
|
expect(container.querySelector('.assistant-panel-image')).not.toBeNull();
|
|
expect(container.querySelector('.assistant-panel-tabs')).not.toBeNull();
|
|
});
|
|
|
|
it('enforces action confirmation policy before dispatching assistant actions', () => {
|
|
const onAction = vi.fn();
|
|
const confirmMock = vi.fn().mockReturnValue(true);
|
|
Object.defineProperty(window, 'confirm', {
|
|
value: confirmMock,
|
|
configurable: true,
|
|
});
|
|
|
|
const { getByText } = render(
|
|
React.createElement(AssistantPanelControls, {
|
|
elements: [{ type: 'action', label: 'Open Settings', action: 'openSettings' }],
|
|
actionPolicies: { openSettings: 'confirm' },
|
|
onAction,
|
|
}),
|
|
);
|
|
|
|
fireEvent.click(getByText('Open Settings'));
|
|
|
|
expect(confirmMock).toHaveBeenCalledTimes(1);
|
|
expect(onAction).toHaveBeenCalledWith('openSettings', undefined);
|
|
});
|
|
});
|