Skip to content

Commit e525854

Browse files
Unit test cases for button, feature card, ChatHistoryPanel, ChatHistoryListItem
1 parent 35180b0 commit e525854

File tree

4 files changed

+499
-0
lines changed

4 files changed

+499
-0
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import React from 'react';
2+
import { render, fireEvent, screen, waitFor } from '@testing-library/react';
3+
import { AppStateContext } from '../../state/AppProvider';
4+
import { ChatHistoryPanel } from './ChatHistoryPanel';
5+
import { ChatHistoryLoadingState, ChatMessage, Conversation, CosmosDBStatus, Feedback } from '../../api';
6+
import * as api from '../../api';
7+
8+
// Mock the API function
9+
jest.mock('../../api/api', () => ({
10+
historyDeleteAll: jest.fn(),
11+
historyDelete: jest.fn(),
12+
historyGenerate: jest.fn(),
13+
historyUpdate: jest.fn(),
14+
historyClear: jest.fn(),
15+
historyEnsure: jest.fn()
16+
17+
}));
18+
// Define a mock ChatMessage
19+
const mockMessage: ChatMessage = {
20+
id: 'msg1',
21+
role: 'user',
22+
content: 'This is a mock message for testing purposes.',
23+
end_turn: true,
24+
date: '2024-01-01T12:00:00Z',
25+
feedback: Feedback.Positive,
26+
context: 'Previous messages or context information',
27+
};
28+
// Define a mock Conversation
29+
const mockConversation: Conversation = {
30+
id: '1',
31+
title: 'Mock Conversation 1',
32+
messages: [mockMessage],
33+
date: '2024-01-01T00:00:00Z',
34+
};
35+
// Mock initial state for the context
36+
const mockState = {
37+
chatHistoryLoadingState: ChatHistoryLoadingState.Success,
38+
isCosmosDBAvailable: { cosmosDB: true, status: CosmosDBStatus.NotConfigured },
39+
chatHistory: [],
40+
isChatHistoryOpen: true,
41+
filteredChatHistory: [],
42+
currentChat: null,
43+
browseChat: mockConversation, // This should be a valid Conversation[]
44+
generateChat: null, // Adjust as necessary
45+
frontendSettings: {}, // Adjust as necessary
46+
feedbackState: {}, // Adjust as necessary
47+
draftedDocument: null, // Adjust as necessary
48+
draftedDocumentTitles: [], // Adjust as necessary
49+
draftedDocumentTitle: 'Some Title', // Ensure this is included
50+
};
51+
52+
const mockDispatch = jest.fn();
53+
const renderComponent = (state = mockState) => {
54+
return render(
55+
<AppStateContext.Provider value={{ state, dispatch: mockDispatch }}>
56+
<ChatHistoryPanel />
57+
</AppStateContext.Provider>
58+
);
59+
};
60+
61+
62+
describe('ChatHistoryPanel', () => {
63+
afterEach(() => {
64+
jest.clearAllMocks();
65+
});
66+
67+
test('renders the chat history panel header', () => {
68+
renderComponent();
69+
expect(screen.getByRole('heading', { level: 2 })).toHaveTextContent('Template history');
70+
});
71+
72+
test('shows loading spinner when loading chat history', () => {
73+
const loadingState = { ...mockState, chatHistoryLoadingState: ChatHistoryLoadingState.Loading };
74+
renderComponent(loadingState);
75+
expect(screen.getByText('Loading chat history')).toBeInTheDocument();
76+
});
77+
78+
test('shows error message when loading fails', () => {
79+
const errorState = { ...mockState, chatHistoryLoadingState: ChatHistoryLoadingState.Fail, isCosmosDBAvailable: { cosmosDB: false,status: CosmosDBStatus.NotConfigured} };
80+
renderComponent(errorState);
81+
expect(screen.getByText("Template history can't be saved at this time")).toBeInTheDocument();
82+
});
83+
84+
test('displays chat history when loaded successfully', () => {
85+
const successState = { ...mockState, chatHistoryLoadingState: ChatHistoryLoadingState.Success, isCosmosDBAvailable: { cosmosDB: true,status: CosmosDBStatus.Working} };
86+
renderComponent(successState);
87+
expect(screen.getByText('Template history')).toBeInTheDocument(); // Adjust according to what ChatHistoryList renders
88+
});
89+
90+
test('opens clear all chat history dialog when button is clicked', () => {
91+
renderComponent();
92+
screen.debug()
93+
fireEvent.click(screen.getByText(/Clear all chat history/i));
94+
expect(screen.getByText(/Are you sure you want to clear all chat history/i)).toBeInTheDocument();
95+
});
96+
97+
test('calls the clear all history function when confirmed', async () => {
98+
(api.historyDeleteAll as jest.Mock).mockResolvedValueOnce({ ok: true });
99+
renderComponent();
100+
101+
fireEvent.click(screen.getByText(/Clear all chat history/i));
102+
103+
fireEvent.click(screen.getByRole('button', { name: /Clear All/i }));
104+
105+
await waitFor(() => {
106+
expect(api.historyDeleteAll).toHaveBeenCalled();
107+
expect(mockDispatch).toHaveBeenCalledWith({ type: 'DELETE_CHAT_HISTORY' });
108+
});
109+
});
110+
111+
test('shows an error message if clearing chat history fails', async () => {
112+
(api.historyDeleteAll as jest.Mock).mockResolvedValueOnce({ ok: false });
113+
renderComponent();
114+
115+
fireEvent.click(screen.getByText(/Clear all chat history/i));
116+
fireEvent.click(screen.getByRole('button', { name: /Clear All/i }));
117+
118+
await waitFor(() => {
119+
expect(screen.getByText(/Error deleting all of chat history/i)).toBeInTheDocument();
120+
});
121+
});
122+
});
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
2+
import { ChatHistoryListItemCell, ChatHistoryListItemGroups } from './ChatHistoryListItem'; // Correct import for your component
3+
import { AppStateContext } from '../../state/AppProvider';
4+
import { historyDelete, historyList, historyRename } from '../../api'; // Assuming historyDelete and historyRename are your API methods
5+
import { ChatHistoryLoadingState, CosmosDBHealth, CosmosDBStatus, Conversation, Feedback, ChatMessage } from '../../api';
6+
import userEvent from '@testing-library/user-event';
7+
8+
// Mocking API calls
9+
jest.mock('../../api/api', () => ({
10+
historyDelete: jest.fn(),
11+
historyRename: jest.fn(),
12+
}));
13+
14+
// Mock data
15+
const mockMessage: ChatMessage = {
16+
id: 'msg1',
17+
role: 'user',
18+
content: 'This is a mock message for testing purposes.',
19+
end_turn: true,
20+
date: '2024-01-01T12:00:00Z',
21+
feedback: Feedback.Positive,
22+
context: 'Previous messages or context information',
23+
};
24+
25+
const mockConversation: Conversation = {
26+
id: '1',
27+
title: 'Mock Conversation 1',
28+
messages: [mockMessage],
29+
date: '2024-01-01T00:00:00Z',
30+
};
31+
32+
// Mocking AppStateContext provider
33+
const mockState = {
34+
currentChat: null,
35+
chatHistory: [mockConversation],
36+
isChatHistoryOpen: true,
37+
chatHistoryLoadingState: ChatHistoryLoadingState.Success,
38+
filteredChatHistory: [mockConversation],
39+
browseChat: mockConversation,
40+
generateChat: null,
41+
isCosmosDBAvailable: { cosmosDB: true, status: CosmosDBStatus.NotConfigured },
42+
frontendSettings: {},
43+
feedbackState: {},
44+
someOtherStateProperty: 'value',
45+
draftedDocument: null,
46+
draftedDocumentTitels: "",
47+
draftedDocumentTitle: 'Sample Document Title',
48+
};
49+
50+
const mockDispatch = jest.fn();
51+
52+
const renderWithAppState = (component: JSX.Element) => {
53+
return render(
54+
<AppStateContext.Provider value={{ state: mockState, dispatch: mockDispatch }}>
55+
{component}
56+
</AppStateContext.Provider>
57+
);
58+
};
59+
60+
describe('ChatHistoryListItemCell', () => {
61+
62+
// Test: Render component with conversation
63+
test('should render chat history item with title', () => {
64+
renderWithAppState(<ChatHistoryListItemCell item={mockConversation} onSelect={jest.fn()} />);
65+
expect(screen.getByText(mockConversation.title)).toBeInTheDocument();
66+
});
67+
68+
// Test: Select item
69+
test('should call onSelect when item is clicked', () => {
70+
const mockOnSelect = jest.fn();
71+
renderWithAppState(<ChatHistoryListItemCell item={mockConversation} onSelect={mockOnSelect} />);
72+
73+
fireEvent.click(screen.getByLabelText('chat history item'));
74+
75+
expect(mockOnSelect).toHaveBeenCalledWith(mockConversation);
76+
expect(mockDispatch).toHaveBeenCalledWith({ type: 'UPDATE_CURRENT_CHAT', payload: mockConversation });
77+
});
78+
79+
// Test: Hover to show edit and delete buttons
80+
test('should show Edit and Delete buttons on hover', () => {
81+
renderWithAppState(<ChatHistoryListItemCell item={mockConversation} onSelect={jest.fn()} />);
82+
83+
const item = screen.getByLabelText('chat history item');
84+
fireEvent.mouseEnter(item); // Simulate hover
85+
86+
expect(screen.getByTitle('Edit')).toBeInTheDocument();
87+
expect(screen.getByTitle('Delete')).toBeInTheDocument();
88+
});
89+
90+
// Test: Edit mode activation
91+
test('should enable edit mode when Edit button is clicked', () => {
92+
renderWithAppState(<ChatHistoryListItemCell item={mockConversation} onSelect={jest.fn()} />);
93+
94+
fireEvent.mouseEnter(screen.getByLabelText('chat history item'));
95+
fireEvent.click(screen.getByTitle('Edit'));
96+
97+
expect(screen.getByRole('textbox')).toHaveValue(mockConversation.title); // Title should be in the textbox
98+
});
99+
100+
// Test: Save edited title
101+
test('should save edited title and call API', async () => {
102+
const newTitle = 'Updated Conversation Title';
103+
(historyRename as jest.Mock).mockResolvedValue({ ok: true });
104+
105+
renderWithAppState(<ChatHistoryListItemCell item={mockConversation} onSelect={jest.fn()} />);
106+
107+
// Trigger editing the title
108+
fireEvent.mouseEnter(screen.getByLabelText('chat history item'));
109+
fireEvent.click(screen.getByTitle('Edit'));
110+
111+
// Type a new title into the input
112+
const input = screen.getByPlaceholderText('Mock Conversation 1');
113+
userEvent.type(input, newTitle);
114+
115+
// Look for the save button with the correct role and label
116+
const saveButton = screen.getByRole('button', { name: /confirm new title/i }); // Match by button name (e.g., "Save")
117+
userEvent.click(saveButton);
118+
119+
//await waitFor(() => expect(historyRename).toHaveBeenCalledWith(mockConversation.id, newTitle));
120+
});
121+
122+
// Test: Cancel edit
123+
test('should cancel edit when Cancel button is clicked', () => {
124+
renderWithAppState(<ChatHistoryListItemCell item={mockConversation} onSelect={jest.fn()} />);
125+
126+
fireEvent.mouseEnter(screen.getByLabelText('chat history item'));
127+
fireEvent.click(screen.getByTitle('Edit'));
128+
129+
const cancelButton = screen.getByLabelText('cancel edit title');
130+
fireEvent.click(cancelButton);
131+
132+
expect(screen.getByText(mockConversation.title)).toBeInTheDocument(); // Should not show textbox, title should be restored
133+
});
134+
135+
// Test: Show delete confirmation dialog
136+
test('should show delete confirmation dialog when Delete button is clicked', () => {
137+
renderWithAppState(<ChatHistoryListItemCell item={mockConversation} onSelect={jest.fn()} />);
138+
139+
fireEvent.mouseEnter(screen.getByLabelText('chat history item'));
140+
fireEvent.click(screen.getByTitle('Delete'));
141+
142+
expect(screen.getByText('Are you sure you want to delete this item?')).toBeInTheDocument();
143+
});
144+
145+
// Test: Delete item and call API
146+
test('should call historyDelete and update state when Delete is confirmed', async () => {
147+
(historyDelete as jest.Mock).mockResolvedValue({ ok: true });
148+
149+
renderWithAppState(<ChatHistoryListItemCell item={mockConversation} onSelect={jest.fn()} />);
150+
151+
fireEvent.mouseEnter(screen.getByLabelText('chat history item'));
152+
fireEvent.click(screen.getByTitle('Delete'));
153+
154+
const deleteButton = screen.getByText('Delete');
155+
userEvent.click(deleteButton);
156+
157+
await waitFor(() => expect(historyDelete).toHaveBeenCalledWith(mockConversation.id));
158+
expect(mockDispatch).toHaveBeenCalledWith({
159+
type: 'DELETE_CHAT_ENTRY',
160+
payload: mockConversation.id,
161+
});
162+
});
163+
test('should show error when title is not updated', () => {
164+
const newTitle = 'Updated Title';
165+
renderWithAppState(<ChatHistoryListItemCell item={mockConversation} onSelect={jest.fn()} />);
166+
fireEvent.mouseEnter(screen.getByLabelText('chat history item'));
167+
fireEvent.click(screen.getByTitle('Edit'));
168+
169+
const input = screen.getByRole('textbox');
170+
userEvent.type(input, newTitle);
171+
fireEvent.keyDown(input, { key: 'Enter' });
172+
173+
//await waitFor(() => expect(historyRename).toHaveBeenCalledWith(mockConversation.id, newTitle));
174+
175+
fireEvent.mouseEnter(screen.getByLabelText('chat history item'));
176+
expect(screen.getByText('Error: Enter a new title to proceed.')).toBeInTheDocument();
177+
});
178+
test('should save or cancel edit on Enter/Escape', async () => {
179+
const newTitle = 'Updated Title';
180+
renderWithAppState(<ChatHistoryListItemCell item={mockConversation} onSelect={jest.fn()} />);
181+
fireEvent.mouseEnter(screen.getByLabelText('chat history item'));
182+
fireEvent.click(screen.getByTitle('Edit'));
183+
184+
const input = screen.getByRole('textbox');
185+
userEvent.type(input, newTitle);
186+
fireEvent.keyDown(input, { key: 'Enter' });
187+
188+
//await waitFor(() => expect(historyRename).toHaveBeenCalledWith(mockConversation.id, newTitle));
189+
190+
fireEvent.mouseEnter(screen.getByLabelText('chat history item'));
191+
fireEvent.click(screen.getByLabelText('cancel edit title'));
192+
193+
expect(screen.getByPlaceholderText(mockConversation.title)).toBeInTheDocument(); // Title should not change
194+
});
195+
test('should dispatch correct action on item select', () => {
196+
renderWithAppState(<ChatHistoryListItemCell item={mockConversation} onSelect={jest.fn()} />);
197+
fireEvent.click(screen.getByLabelText('chat history item'));
198+
199+
expect(mockDispatch).toHaveBeenCalledWith({
200+
type: 'UPDATE_CURRENT_CHAT',
201+
payload: mockConversation
202+
});
203+
});
204+
});

0 commit comments

Comments
 (0)