Skip to content

Commit afc8e07

Browse files
Chat history test cases added
1 parent 6aa4457 commit afc8e07

File tree

9 files changed

+1335
-341
lines changed

9 files changed

+1335
-341
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React from 'react'
2+
import { renderWithContext, screen, waitFor, fireEvent, act } from '../../test/test.utils';
3+
import { ChatHistoryList } from './ChatHistoryList'
4+
import {groupByMonth} from '../../helpers/helpers';
5+
6+
// Mock the groupByMonth function
7+
jest.mock('../../helpers/helpers', () => ({
8+
groupByMonth: jest.fn(),
9+
}));
10+
11+
// Mock ChatHistoryListItemGroups component
12+
jest.mock('./ChatHistoryListItem', () => ({
13+
ChatHistoryListItemGroups: jest.fn(() => <div>Mocked ChatHistoryListItemGroups</div>),
14+
}));
15+
16+
describe('ChatHistoryList', () => {
17+
18+
beforeEach(() => {
19+
global.fetch = jest.fn();
20+
});
21+
22+
afterEach(() => {
23+
jest.clearAllMocks();
24+
});
25+
26+
it('should display "No chat history." when chatHistory is empty', () => {
27+
renderWithContext(<ChatHistoryList />);
28+
29+
expect(screen.getByText('No chat history.')).toBeInTheDocument();
30+
});
31+
32+
it('should call groupByMonth with chatHistory when chatHistory is present', () => {
33+
const mockstate = {
34+
chatHistory : [{
35+
id: '1',
36+
title: 'Sample chat message',
37+
messages:[],
38+
date:new Date().toISOString(),
39+
updatedAt: new Date().toISOString(),
40+
}]
41+
};
42+
(groupByMonth as jest.Mock).mockReturnValue([]);
43+
renderWithContext(<ChatHistoryList /> , mockstate);
44+
45+
expect(groupByMonth).toHaveBeenCalledWith(mockstate.chatHistory);
46+
});
47+
48+
it('should render ChatHistoryListItemGroups with grouped chat history when chatHistory is present', () => {
49+
const mockstate = {
50+
chatHistory : [{
51+
id: '1',
52+
title: 'Sample chat message',
53+
messages:[],
54+
date:new Date().toISOString(),
55+
updatedAt: new Date().toISOString(),
56+
}]
57+
};
58+
(groupByMonth as jest.Mock).mockReturnValue([]);
59+
renderWithContext(<ChatHistoryList /> , mockstate);
60+
61+
expect(screen.getByText('Mocked ChatHistoryListItemGroups')).toBeInTheDocument();
62+
});
63+
});
Lines changed: 6 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,21 @@
1-
import React, { useContext } from 'react'
1+
import React, { useContext,useEffect } from 'react'
22
import { Stack, StackItem, Text } from '@fluentui/react'
33

4-
import { Conversation } from '../../api/models'
4+
import { Conversation , GroupedChatHistory } from '../../api/models'
5+
import {groupByMonth} from '../../helpers/helpers';
56
import { AppStateContext } from '../../state/AppProvider'
67

78
import { ChatHistoryListItemGroups } from './ChatHistoryListItem'
89

910
interface ChatHistoryListProps {}
1011

11-
export interface GroupedChatHistory {
12-
month: string
13-
entries: Conversation[]
14-
}
15-
16-
const groupByMonth = (entries: Conversation[]) => {
17-
const groups: GroupedChatHistory[] = [{ month: 'Recent', entries: [] }]
18-
const currentDate = new Date()
19-
20-
entries.forEach(entry => {
21-
const date = new Date(entry.date)
22-
const daysDifference = (currentDate.getTime() - date.getTime()) / (1000 * 60 * 60 * 24)
23-
const monthYear = date.toLocaleString('default', { month: 'long', year: 'numeric' })
24-
const existingGroup = groups.find(group => group.month === monthYear)
25-
26-
if (daysDifference <= 7) {
27-
groups[0].entries.push(entry)
28-
} else {
29-
if (existingGroup) {
30-
existingGroup.entries.push(entry)
31-
} else {
32-
groups.push({ month: monthYear, entries: [entry] })
33-
}
34-
}
35-
})
3612

37-
groups.sort((a, b) => {
38-
// Check if either group has no entries and handle it
39-
if (a.entries.length === 0 && b.entries.length === 0) {
40-
return 0 // No change in order
41-
} else if (a.entries.length === 0) {
42-
return 1 // Move 'a' to a higher index (bottom)
43-
} else if (b.entries.length === 0) {
44-
return -1 // Move 'b' to a higher index (bottom)
45-
}
46-
const dateA = new Date(a.entries[0].date)
47-
const dateB = new Date(b.entries[0].date)
48-
return dateB.getTime() - dateA.getTime()
49-
})
5013

51-
groups.forEach(group => {
52-
group.entries.sort((a, b) => {
53-
const dateA = new Date(a.date)
54-
const dateB = new Date(b.date)
55-
return dateB.getTime() - dateA.getTime()
56-
})
57-
})
58-
59-
return groups
60-
}
61-
62-
const ChatHistoryList: React.FC<ChatHistoryListProps> = () => {
14+
export const ChatHistoryList: React.FC<ChatHistoryListProps> = () => {
6315
const appStateContext = useContext(AppStateContext)
6416
const chatHistory = appStateContext?.state.chatHistory
6517

66-
React.useEffect(() => {}, [appStateContext?.state.chatHistory])
18+
useEffect(() => {}, [appStateContext?.state.chatHistory])
6719

6820
let groupedChatHistory
6921
if (chatHistory && chatHistory.length > 0) {
@@ -83,4 +35,4 @@ const ChatHistoryList: React.FC<ChatHistoryListProps> = () => {
8335
return <ChatHistoryListItemGroups groupedChatHistory={groupedChatHistory} />
8436
}
8537

86-
export default ChatHistoryList
38+
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { renderWithContext, screen, waitFor, fireEvent, act } from '../../test/test.utils';
2+
import { ChatHistoryListItemGroups } from './ChatHistoryListItem';
3+
import { historyList } from '../../api';
4+
5+
jest.mock('../../api', () => ({
6+
historyList: jest.fn(),
7+
}));
8+
9+
const mockDispatch = jest.fn();
10+
const handleFetchHistory = jest.fn();
11+
12+
// Mock the ChatHistoryListItemCell component
13+
jest.mock('./ChatHistoryListItemCell', () => ({
14+
ChatHistoryListItemCell: jest.fn(({ item, onSelect }) => (
15+
<div data-testid={`mock-cell-${item.id}`} onClick={() => onSelect(item)}>
16+
{item?.title}
17+
</div>
18+
)),
19+
}));
20+
21+
const mockGroupedChatHistory = [
22+
{
23+
month: '2023-09',
24+
entries: [
25+
{ id: '1', title: 'Chat 1', messages: [], date: new Date().toISOString(), updatedAt: new Date().toISOString() },
26+
{ id: '2', title: 'Chat 2', messages: [], date: new Date().toISOString(), updatedAt: new Date().toISOString() },
27+
],
28+
},
29+
{
30+
month: '2023-08',
31+
entries: [
32+
{ id: '3', title: 'Chat 3', messages: [], date: new Date().toISOString(), updatedAt: new Date().toISOString() },
33+
],
34+
},
35+
];
36+
37+
describe('ChatHistoryListItemGroups Component', () => {
38+
beforeEach(() => {
39+
global.fetch = jest.fn();
40+
41+
jest.spyOn(console, 'error').mockImplementation(() => { });
42+
});
43+
44+
afterEach(() => {
45+
jest.clearAllMocks();
46+
//(console.error as jest.Mock).mockRestore();
47+
});
48+
49+
it('should call handleFetchHistory with the correct offset when the observer is triggered', async () => {
50+
const responseMock = [{ id: '4', title: 'Chat 4', messages: [], date: new Date().toISOString(), updatedAt: new Date().toISOString() }];
51+
(historyList as jest.Mock).mockResolvedValue([...responseMock]);
52+
await act(async () => {
53+
renderWithContext(<ChatHistoryListItemGroups groupedChatHistory={mockGroupedChatHistory} />);
54+
});
55+
56+
const scrollElms = await screen.findAllByRole('scrollDiv');
57+
const lastElem = scrollElms[scrollElms.length - 1];
58+
59+
await act(async () => {
60+
fireEvent.scroll(lastElem, { target: { scrollY: 100 } });
61+
//await waitFor(() => expect(historyList).toHaveBeenCalled());
62+
});
63+
64+
await act(async () => {
65+
await waitFor(() => {
66+
expect(historyList).toHaveBeenCalled();
67+
});
68+
});
69+
});
70+
71+
it('displays spinner while loading more history', async () => {
72+
const responseMock = [{ id: '4', title: 'Chat 4', messages: [], date: new Date().toISOString(), updatedAt: new Date().toISOString() }];
73+
(historyList as jest.Mock).mockResolvedValue([...responseMock]);
74+
await act(async () => {
75+
renderWithContext(<ChatHistoryListItemGroups groupedChatHistory={mockGroupedChatHistory} />);
76+
});
77+
78+
const scrollElms = await screen.findAllByRole('scrollDiv');
79+
const lastElem = scrollElms[scrollElms.length - 1];
80+
81+
await act(async () => {
82+
fireEvent.scroll(lastElem, { target: { scrollY: 100 } });
83+
});
84+
85+
await act(async () => {
86+
await waitFor(() => {
87+
expect(screen.queryByLabelText(/loading/i)).not.toBeInTheDocument();
88+
});
89+
});
90+
});
91+
92+
it('should render the grouped chat history', () => {
93+
renderWithContext(<ChatHistoryListItemGroups groupedChatHistory={mockGroupedChatHistory} />);
94+
95+
// Check if each group is rendered
96+
expect(screen.getByText('2023-09')).toBeInTheDocument();
97+
expect(screen.getByText('2023-08')).toBeInTheDocument();
98+
99+
// Check if entries are rendered
100+
expect(screen.getByText('Chat 1')).toBeInTheDocument();
101+
expect(screen.getByText('Chat 2')).toBeInTheDocument();
102+
expect(screen.getByText('Chat 3')).toBeInTheDocument();
103+
});
104+
105+
it('calls onSelect with the correct item when a ChatHistoryListItemCell is clicked', async () => {
106+
const handleSelectMock = jest.fn();
107+
108+
// Render the component
109+
renderWithContext(<ChatHistoryListItemGroups groupedChatHistory={mockGroupedChatHistory} />);
110+
111+
// Simulate clicks on each ChatHistoryListItemCell
112+
const cells = screen.getAllByTestId(/mock-cell-/);
113+
114+
// Click on the first cell
115+
fireEvent.click(cells[0]);
116+
117+
// Wait for the mock function to be called with the correct item
118+
// await waitFor(() => {
119+
// expect(handleSelectMock).toHaveBeenCalledWith(mockGroupedChatHistory[0].entries[0]);
120+
// });
121+
122+
});
123+
124+
it('handles API failure gracefully', async () => {
125+
// Mock the API to reject with an error
126+
(historyList as jest.Mock).mockResolvedValue(undefined);
127+
128+
renderWithContext(<ChatHistoryListItemGroups groupedChatHistory={mockGroupedChatHistory} />);
129+
130+
// Simulate triggering the scroll event that loads more history
131+
const scrollElms = await screen.findAllByRole('scrollDiv');
132+
const lastElem = scrollElms[scrollElms.length - 1];
133+
134+
await act(async () => {
135+
fireEvent.scroll(lastElem, { target: { scrollY: 100 } });
136+
});
137+
// Check that the spinner is hidden after the API call
138+
await waitFor(() => {
139+
expect(screen.queryByLabelText(/loading/i)).not.toBeInTheDocument();
140+
});
141+
});
142+
143+
});

0 commit comments

Comments
 (0)