test(7.2): add tests for AI components
- ChatWindow: messages, input, send button, streaming, error display - SessionList: sessions, folders, click handler, loading/error states - SuggestionSidebar: suggestions list, filters, dismiss, close button - AISettings: tabs (providers, presets, agents, tools), provider list - ProactiveAISettings: toggle, categories, confidence, rate limit, model All 40 AI tests passing.
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
|
||||
vi.mock('@/api/ai', () => ({
|
||||
fetchProviders: vi.fn().mockResolvedValue([
|
||||
{ id: 'p1', name: 'OpenAI', provider_type: 'openai', api_key: 'sk-***', base_url: '', is_default: true },
|
||||
{ id: 'p2', name: 'Ollama', provider_type: 'ollama', api_key: '', base_url: 'http://localhost:11434', is_default: false },
|
||||
]),
|
||||
createProvider: vi.fn().mockResolvedValue({ id: 'p3' }),
|
||||
updateProvider: vi.fn().mockResolvedValue({}),
|
||||
deleteProvider: vi.fn().mockResolvedValue({}),
|
||||
fetchPresets: vi.fn().mockResolvedValue([
|
||||
{ id: 'pr1', name: 'GPT-4o', model_id: 'gpt-4o', provider_id: 'p1', temperature: 0.7, max_tokens: 2048, top_p: 1.0, system_prompt: '' },
|
||||
]),
|
||||
createPreset: vi.fn().mockResolvedValue({ id: 'pr2' }),
|
||||
updatePreset: vi.fn().mockResolvedValue({}),
|
||||
deletePreset: vi.fn().mockResolvedValue({}),
|
||||
fetchAgents: vi.fn().mockResolvedValue([
|
||||
{ id: 'a1', name: 'Default Agent', description: 'Default assistant', system_prompt: 'You are helpful', preset_id: 'pr1', tool_ids: ['search'], is_default: true },
|
||||
]),
|
||||
createAgent: vi.fn().mockResolvedValue({ id: 'a2' }),
|
||||
updateAgent: vi.fn().mockResolvedValue({}),
|
||||
deleteAgent: vi.fn().mockResolvedValue({}),
|
||||
fetchTools: vi.fn().mockResolvedValue([
|
||||
{ name: 'search', description: 'Search tool', plugin_name: 'core', category: 'utility', required_permission: null },
|
||||
]),
|
||||
}));
|
||||
|
||||
import { AISettingsPage } from '@/pages/AISettings';
|
||||
|
||||
describe('AISettingsPage', () => {
|
||||
it('renders AI settings page with title', () => {
|
||||
render(<AISettingsPage />);
|
||||
expect(screen.getByText('KI Assistent Einstellungen')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders provider tab', () => {
|
||||
render(<AISettingsPage />);
|
||||
expect(screen.getByText('Anbieter')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders presets tab', () => {
|
||||
render(<AISettingsPage />);
|
||||
expect(screen.getByText('Modelle & Presets')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders agents tab', () => {
|
||||
render(<AISettingsPage />);
|
||||
expect(screen.getByText('Agenten')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders tools tab', () => {
|
||||
render(<AISettingsPage />);
|
||||
expect(screen.getByText('Tools')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders provider list after loading', async () => {
|
||||
render(<AISettingsPage />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('OpenAI')).toBeInTheDocument();
|
||||
expect(screen.getByText('Ollama')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders add provider button', async () => {
|
||||
render(<AISettingsPage />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('+ Hinzufügen')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders edit and delete buttons for providers', async () => {
|
||||
render(<AISettingsPage />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByText('Bearbeiten').length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,113 @@
|
||||
import React from 'react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { ChatWindow } from '@/components/ai/ChatWindow';
|
||||
|
||||
const mockMessages = [
|
||||
{ id: 'm1', session_id: 's1', role: 'user', content: 'Hello AI', tokens: 10, model_used: '' },
|
||||
{ id: 'm2', session_id: 's1', role: 'assistant', content: 'Hello User', tokens: 10, model_used: 'gpt-4' },
|
||||
];
|
||||
|
||||
const fetchMessagesMock = vi.fn();
|
||||
const fetchAttachmentsMock = vi.fn();
|
||||
const streamChatMock = vi.fn();
|
||||
const uploadAttachmentMock = vi.fn();
|
||||
|
||||
vi.mock('@/api/ai', () => ({
|
||||
fetchMessages: (...args: any[]) => fetchMessagesMock(...args),
|
||||
fetchAttachments: (...args: any[]) => fetchAttachmentsMock(...args),
|
||||
streamChat: (...args: any[]) => streamChatMock(...args),
|
||||
uploadAttachment: (...args: any[]) => uploadAttachmentMock(...args),
|
||||
getAttachmentDownloadUrl: (id: string) => `/api/v1/ai/attachments/${id}/download`,
|
||||
}));
|
||||
|
||||
vi.mock('react-markdown', () => ({
|
||||
default: ({ children }: { children: string }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('remark-gfm', () => ({
|
||||
default: () => ({}),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
fetchMessagesMock.mockResolvedValue(mockMessages);
|
||||
fetchAttachmentsMock.mockResolvedValue([]);
|
||||
streamChatMock.mockReturnValue({
|
||||
async *[Symbol.asyncIterator]() {
|
||||
yield { type: 'token', content: 'Hi' };
|
||||
yield { type: 'done' };
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('ChatWindow', () => {
|
||||
it('renders chat window with messages', async () => {
|
||||
render(<ChatWindow sessionId="s1" />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Hello AI')).toBeInTheDocument();
|
||||
expect(screen.getByText('Hello User')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders textarea input for message', async () => {
|
||||
render(<ChatWindow sessionId="s1" />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByPlaceholderText('Nachricht eingeben...')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders send button', async () => {
|
||||
render(<ChatWindow sessionId="s1" />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Senden')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('disables send button when input is empty', async () => {
|
||||
render(<ChatWindow sessionId="s1" />);
|
||||
await waitFor(() => {
|
||||
const sendBtn = screen.getByText('Senden');
|
||||
expect(sendBtn).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
it('enables send button when input has text', async () => {
|
||||
render(<ChatWindow sessionId="s1" />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByPlaceholderText('Nachricht eingeben...')).toBeInTheDocument();
|
||||
});
|
||||
const input = screen.getByPlaceholderText('Nachricht eingeben...');
|
||||
fireEvent.change(input, { target: { value: 'Test message' } });
|
||||
expect(screen.getByText('Senden')).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it('shows empty state when no messages', async () => {
|
||||
fetchMessagesMock.mockResolvedValueOnce([]);
|
||||
render(<ChatWindow sessionId="s1" />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Starte eine Konversation...')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('displays error when fetchMessages fails', async () => {
|
||||
fetchMessagesMock.mockRejectedValueOnce(new Error('Load failed'));
|
||||
render(<ChatWindow sessionId="s1" />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Load failed')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('sends message on button click', async () => {
|
||||
render(<ChatWindow sessionId="s1" />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByPlaceholderText('Nachricht eingeben...')).toBeInTheDocument();
|
||||
});
|
||||
const input = screen.getByPlaceholderText('Nachricht eingeben...');
|
||||
fireEvent.change(input, { target: { value: 'Test message' } });
|
||||
fireEvent.click(screen.getByText('Senden'));
|
||||
await waitFor(() => {
|
||||
expect(streamChatMock).toHaveBeenCalledWith('s1', 'Test message', undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,107 @@
|
||||
import React from 'react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { ProactiveAISettings } from '@/pages/ProactiveAISettings';
|
||||
|
||||
const mockSettings = {
|
||||
enabled: true,
|
||||
suggestion_categories: ['mail', 'tasks'],
|
||||
confidence_threshold: 0.7,
|
||||
rate_limit_seconds: 15,
|
||||
model: 'ollama/deepseek-v4',
|
||||
heartbeat_enabled: true,
|
||||
heartbeat_interval_seconds: 300,
|
||||
heartbeat_target_room: 'Live KI',
|
||||
};
|
||||
|
||||
const apiGetMock = vi.fn();
|
||||
const apiPutMock = vi.fn();
|
||||
|
||||
vi.mock('@/api/client', () => ({
|
||||
apiClient: {
|
||||
get: (...args: any[]) => apiGetMock(...args),
|
||||
post: vi.fn().mockResolvedValue({ data: {} }),
|
||||
put: (...args: any[]) => apiPutMock(...args),
|
||||
},
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
apiGetMock.mockResolvedValue({ data: mockSettings });
|
||||
apiPutMock.mockResolvedValue({ data: mockSettings });
|
||||
});
|
||||
|
||||
describe('ProactiveAISettings', () => {
|
||||
it('renders proactive AI settings with title', async () => {
|
||||
render(<ProactiveAISettings />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Proaktive KI')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders enable/disable toggle', async () => {
|
||||
render(<ProactiveAISettings />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Proaktive KI')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders category checkboxes', async () => {
|
||||
render(<ProactiveAISettings />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('E-Mails')).toBeInTheDocument();
|
||||
expect(screen.getByText('Aufgaben')).toBeInTheDocument();
|
||||
expect(screen.getByText('Kontakte')).toBeInTheDocument();
|
||||
expect(screen.getByText('Firmen')).toBeInTheDocument();
|
||||
expect(screen.getByText('Erkenntnisse')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders confidence threshold section', async () => {
|
||||
render(<ProactiveAISettings />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Confidence Schwellwert')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders rate limit section', async () => {
|
||||
render(<ProactiveAISettings />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Rate Limit')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders model selection dropdown', async () => {
|
||||
render(<ProactiveAISettings />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('KI Modell (Ollama Cloud)')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders heartbeat section', async () => {
|
||||
render(<ProactiveAISettings />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Heartbeat')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders loading state when settings not loaded', () => {
|
||||
apiGetMock.mockReturnValue(new Promise(() => {}));
|
||||
render(<ProactiveAISettings />);
|
||||
expect(screen.getByText('Lade Einstellungen...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows confidence percentage', async () => {
|
||||
render(<ProactiveAISettings />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('70%')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows rate limit value', async () => {
|
||||
render(<ProactiveAISettings />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('15s')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,93 @@
|
||||
import React from 'react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { SessionList } from '@/components/ai/SessionList';
|
||||
|
||||
const mockSessions = [
|
||||
{ id: 's1', title: 'First Chat', folder_id: null, agent_id: null, is_sidebar: false, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' },
|
||||
{ id: 's2', title: 'Second Chat', folder_id: 'f1', agent_id: null, is_sidebar: false, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' },
|
||||
];
|
||||
|
||||
const mockFolders = [
|
||||
{ id: 'f1', name: 'My Folder', parent_id: null, created_at: '2024-01-01T00:00:00Z' },
|
||||
];
|
||||
|
||||
const fetchSessionsMock = vi.fn();
|
||||
const fetchFoldersMock = vi.fn();
|
||||
const createSessionMock = vi.fn();
|
||||
const deleteSessionMock = vi.fn();
|
||||
const updateSessionMock = vi.fn();
|
||||
const createFolderMock = vi.fn();
|
||||
const deleteFolderMock = vi.fn();
|
||||
const updateFolderMock = vi.fn();
|
||||
|
||||
vi.mock('@/api/ai', () => ({
|
||||
fetchSessions: (...args: any[]) => fetchSessionsMock(...args),
|
||||
fetchFolders: (...args: any[]) => fetchFoldersMock(...args),
|
||||
createSession: (...args: any[]) => createSessionMock(...args),
|
||||
deleteSession: (...args: any[]) => deleteSessionMock(...args),
|
||||
updateSession: (...args: any[]) => updateSessionMock(...args),
|
||||
createFolder: (...args: any[]) => createFolderMock(...args),
|
||||
deleteFolder: (...args: any[]) => deleteFolderMock(...args),
|
||||
updateFolder: (...args: any[]) => updateFolderMock(...args),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
fetchSessionsMock.mockResolvedValue(mockSessions);
|
||||
fetchFoldersMock.mockResolvedValue(mockFolders);
|
||||
createSessionMock.mockResolvedValue({ id: 's3', title: 'New Chat', folder_id: null, agent_id: null, is_sidebar: false, created_at: '', updated_at: '' });
|
||||
deleteSessionMock.mockResolvedValue({});
|
||||
updateSessionMock.mockResolvedValue({});
|
||||
createFolderMock.mockResolvedValue({ id: 'f2', name: 'New Folder', parent_id: null, created_at: '' });
|
||||
deleteFolderMock.mockResolvedValue({});
|
||||
updateFolderMock.mockResolvedValue({});
|
||||
});
|
||||
|
||||
describe('SessionList', () => {
|
||||
it('renders session list with sessions', async () => {
|
||||
render(<SessionList activeSessionId="s1" onSelectSession={vi.fn()} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('First Chat')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders folder names', async () => {
|
||||
render(<SessionList activeSessionId="s1" onSelectSession={vi.fn()} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('My Folder')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders sessions inside folders', async () => {
|
||||
render(<SessionList activeSessionId="s1" onSelectSession={vi.fn()} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Second Chat')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls onSelectSession when session clicked', async () => {
|
||||
const onSelect = vi.fn();
|
||||
render(<SessionList activeSessionId={null} onSelectSession={onSelect} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('First Chat')).toBeInTheDocument();
|
||||
});
|
||||
fireEvent.click(screen.getByText('First Chat'));
|
||||
expect(onSelect).toHaveBeenCalledWith('s1');
|
||||
});
|
||||
|
||||
it('shows loading state initially', () => {
|
||||
fetchSessionsMock.mockReturnValue(new Promise(() => {}));
|
||||
fetchFoldersMock.mockReturnValue(new Promise(() => {}));
|
||||
render(<SessionList activeSessionId={null} onSelectSession={vi.fn()} />);
|
||||
expect(screen.getByText('Laden...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays error when fetch fails', async () => {
|
||||
fetchSessionsMock.mockRejectedValueOnce(new Error('Failed to load'));
|
||||
render(<SessionList activeSessionId={null} onSelectSession={vi.fn()} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Failed to load')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,127 @@
|
||||
import React from 'react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { SuggestionList, SuggestionSidebar } from '@/components/ai/SuggestionSidebar';
|
||||
|
||||
const mockSuggestions = [
|
||||
{
|
||||
id: 'sug1',
|
||||
entity_type: 'mail',
|
||||
entity_id: 'm1',
|
||||
suggestion_type: 'info',
|
||||
title: 'Email suggestion',
|
||||
content: 'You have an important email',
|
||||
confidence: 0.85,
|
||||
actions: [{ method: 'POST', path: '/mail/read', body: {}, description: 'Mark as read' }],
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
is_dismissed: false,
|
||||
is_acted_upon: false,
|
||||
},
|
||||
{
|
||||
id: 'sug2',
|
||||
entity_type: 'task',
|
||||
entity_id: 't1',
|
||||
suggestion_type: 'warning',
|
||||
title: 'Task overdue',
|
||||
content: 'Task is overdue',
|
||||
confidence: 0.92,
|
||||
actions: [],
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
is_dismissed: false,
|
||||
is_acted_upon: false,
|
||||
},
|
||||
];
|
||||
|
||||
const apiGetMock = vi.fn();
|
||||
const apiPostMock = vi.fn();
|
||||
const apiPutMock = vi.fn();
|
||||
|
||||
vi.mock('@/api/client', () => ({
|
||||
apiClient: {
|
||||
get: (...args: any[]) => apiGetMock(...args),
|
||||
post: (...args: any[]) => apiPostMock(...args),
|
||||
put: (...args: any[]) => apiPutMock(...args),
|
||||
},
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
apiGetMock.mockResolvedValue({ data: { items: mockSuggestions } });
|
||||
apiPostMock.mockResolvedValue({ data: {} });
|
||||
apiPutMock.mockResolvedValue({ data: {} });
|
||||
});
|
||||
|
||||
describe('SuggestionList', () => {
|
||||
it('renders suggestion list container', async () => {
|
||||
render(<SuggestionList />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('suggestion-list')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders suggestion titles', async () => {
|
||||
render(<SuggestionList />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Email suggestion')).toBeInTheDocument();
|
||||
expect(screen.getByText('Task overdue')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders filter buttons', async () => {
|
||||
render(<SuggestionList />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Alle')).toBeInTheDocument();
|
||||
expect(screen.getByText('Info')).toBeInTheDocument();
|
||||
expect(screen.getByText('Warnung')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders suggestion count', async () => {
|
||||
render(<SuggestionList />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/2 Vorschläge/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows empty state when no suggestions', async () => {
|
||||
apiGetMock.mockResolvedValueOnce({ data: { items: [] } });
|
||||
render(<SuggestionList />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Keine Vorschläge vorhanden')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('filters suggestions by type', async () => {
|
||||
render(<SuggestionList />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Email suggestion')).toBeInTheDocument();
|
||||
});
|
||||
fireEvent.click(screen.getByText('Warnung'));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Task overdue')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Email suggestion')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('SuggestionSidebar', () => {
|
||||
it('renders sidebar with close button when open', async () => {
|
||||
render(<SuggestionSidebar isOpen={true} onClose={vi.fn()} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('KI Vorschläge')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('✕').length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('calls onClose when close button clicked', async () => {
|
||||
const onClose = vi.fn();
|
||||
render(<SuggestionSidebar isOpen={true} onClose={onClose} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('KI Vorschläge')).toBeInTheDocument();
|
||||
});
|
||||
// The close button is the first ✕ in the sidebar header
|
||||
const closeButtons = screen.getAllByText('✕');
|
||||
fireEvent.click(closeButtons[0]);
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user