Skip to content

Commit 0776196

Browse files
authored
Added few more tests & re-organizing components (#6)
This pull request focuses on refactoring and organizing the codebase by restructuring component imports and adding new tests. The changes include moving components to more appropriate directories, updating import paths, and introducing new test cases for various components. ### Restructuring and Import Path Updates: * [`src/App.tsx`](diffhunk://#diff-26ad4b834941d9b19ebf9db8082bd202aaf72ea0ddea85f5a8a0cb3c729cc6f2L7-R14): Updated import paths for several components to reflect their new locations. * [`src/components/ChatContainer/ChatContainer.tsx`](diffhunk://#diff-c0bfccaf03a29a5059f6a47c6dd2cdd194fcb1e06fe8e0e188d700a86204f073L2-R7): Moved and updated import paths for related components and services. * [`src/components/ChatInput/ChatInput.tsx`](diffhunk://#diff-e56b5877d49403da7c452c9efab46ee9a6e3daa2be72e4aeca9bf085afd81b17L2-R3): Moved and updated import paths for related components. * [`src/components/Notification/NotificationContainer.tsx`](diffhunk://#diff-a7a3cccb312c200f36070589f838d8f5b6a55edcfbb0a3ebcacc18e664b6484fL4-R4): Moved and updated import paths for related components. * [`src/components/LLMProviderModal/LLMProviderCard.tsx`](diffhunk://#diff-e394d9143f494a3f02408949ee230b3809f82abba6e7fc0d51fb5b9d14ac82e4L2-R2): Moved and updated import paths for related components. ### New Test Cases: * [`src/components/ChatInputButton/ToolsModal.test.tsx`](diffhunk://#diff-5403659b7ee64a58dcfa7026e4f58e57a731ed7d0cd222e7c4391912bd6d10afR1-R160): Added comprehensive test cases for the `ToolsModal` component, including rendering, tool selection, and search functionality. * [`src/components/ChatInputButton/ToolsToggle.test.tsx`](diffhunk://#diff-c3ddc66de5ee3885a6fd20e64b040a3b73a35c93ffdb2ea88467ed3a23900c3aR1-R103): Added test cases for the `ToolsToggle` component, covering default state, modal interactions, and tool selection. * [`src/components/ToolItem/ToolItem.test.tsx`](diffhunk://#diff-50efcfb2bb4d7be54bc5f43c55fe0d062101d5540f5bcdcb4d5e8b3f5fb3573aR1-R110): Added test cases for the `ToolItem` component to verify rendering, interactions, and accessibility. ### Context and Hook Refactoring: * [`src/context/NotificationContext.tsx`](diffhunk://#diff-e2024b556973c951914a0c8a9d8fb6eb5e812c6fe3b6269feb566485b432315bL1-R3): Simplified the context by moving the context type definition to a separate file and removing the `useNotification` hook. [[1]](diffhunk://#diff-e2024b556973c951914a0c8a9d8fb6eb5e812c6fe3b6269feb566485b432315bL1-R3) [[2]](diffhunk://#diff-e2024b556973c951914a0c8a9d8fb6eb5e812c6fe3b6269feb566485b432315bL38-L45) * [`src/context/NotificationContextType.ts`](diffhunk://#diff-1070b556e73ee8d66994a7dc474a797cc961833f8ee97c2431308d5e55073ad8R1-R10): Created a new file to define the `NotificationContextType` and export the `NotificationContext`. * [`src/context/useNotification.ts`](diffhunk://#diff-18849e1183bb9c1c4ba93f688952eb08c8bfccd5bb6a792e072e847afe61d88eR1-R10): Created a new file for the `useNotification` hook to utilize the refactored `NotificationContext`.This pull request includes several changes mainly focused on file restructuring and improving test coverage. The most important changes include renaming and moving various components to new directories, updating import paths accordingly, and adding comprehensive tests for multiple components. ### File Restructuring: * [`src/App.tsx`](diffhunk://#diff-26ad4b834941d9b19ebf9db8082bd202aaf72ea0ddea85f5a8a0cb3c729cc6f2L7-R14): Updated import paths for several components to reflect their new locations. * [`src/components/ChatContainer/ChatContainer.tsx`](diffhunk://#diff-c0bfccaf03a29a5059f6a47c6dd2cdd194fcb1e06fe8e0e188d700a86204f073L2-R7): Renamed from `src/components/ChatContainer.tsx` and updated import paths. * [`src/components/ChatInput/ChatInput.tsx`](diffhunk://#diff-e56b5877d49403da7c452c9efab46ee9a6e3daa2be72e4aeca9bf085afd81b17L2-R3): Renamed from `src/components/ChatInput.tsx` and updated import paths. * [`src/components/LLMProviderModal/LLMProviderCard.tsx`](diffhunk://#diff-e394d9143f494a3f02408949ee230b3809f82abba6e7fc0d51fb5b9d14ac82e4L2-R2): Renamed from `src/components/LLMProviderCard.tsx` and updated import paths. * [`src/components/Notification/NotificationContainer.tsx`](diffhunk://#diff-a7a3cccb312c200f36070589f838d8f5b6a55edcfbb0a3ebcacc18e664b6484fL4-R4): Renamed from `src/components/NotificationContainer.tsx` and updated import paths. ### Test Additions: * [`src/components/ChatInputButton/ToolsModal.test.tsx`](diffhunk://#diff-5403659b7ee64a58dcfa7026e4f58e57a731ed7d0cd222e7c4391912bd6d10afR1-R155): Added tests for `ToolsModal` component, including rendering, fetching tools, filtering tools, handling selection and deselection, and closing without saving. * [`src/components/ChatInputButton/ToolsToggle.test.tsx`](diffhunk://#diff-c3ddc66de5ee3885a6fd20e64b040a3b73a35c93ffdb2ea88467ed3a23900c3aR1-R103): Added tests for `ToolsToggle` component, covering default state, selected tools count, modal interactions, and tool change callbacks. * [`src/components/ToolItem/ToolItem.test.tsx`](diffhunk://#diff-50efcfb2bb4d7be54bc5f43c55fe0d062101d5540f5bcdcb4d5e8b3f5fb3573aR1-R110): Added tests for `ToolItem` component, including rendering, accessibility attributes, and interaction handling. These changes ensure that the codebase is better organized and more thoroughly tested, improving maintainability and reliability.
1 parent c652b7e commit 0776196

19 files changed

+417
-41
lines changed

src/App.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import DOMPurify from 'dompurify';
44
import hljs from 'highlight.js';
55
import 'highlight.js/styles/github-dark.css';
66
import {Sidebar} from './components/Sidebar';
7-
import {ModelControls} from './components/ModelControls';
8-
import {ChatContainer} from './components/ChatContainer';
9-
import {WelcomeScreen} from './components/WelcomeScreen';
7+
import {WelcomeScreen} from './components/WelcomeScreen/WelcomeScreen.tsx';
108
import {Cog6ToothIcon} from "@heroicons/react/24/outline";
119
import {NotificationProvider} from './context/NotificationContext';
12-
import {NotificationContainer} from "./components/NotificationContainer.tsx";
1310
import {Auth0Provider} from '@auth0/auth0-react';
1411
import AIIcon from "./components/AIIcon.tsx";
12+
import {ChatContainer} from "./components/ChatContainer/ChatContainer.tsx";
13+
import {ModelControls} from "./components/ModelControls/ModelControls.tsx";
14+
import {NotificationContainer} from "./components/Notification/NotificationContainer.tsx";
1515

1616
interface ModelSettings {
1717
temperature: number;

src/components/ChatContainer.tsx renamed to src/components/ChatContainer/ChatContainer.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import React, {useEffect, useRef, useState} from 'react';
2-
import {Message} from "./Message/Message.tsx";
3-
import {ChatInput} from "./ChatInput.tsx";
4-
import {ChatPayload} from '../types/chat';
5-
import {useNotification} from "../context/NotificationContext.tsx";
62
import {useAuth0} from '@auth0/auth0-react';
7-
import {ApiChatMessage, ChatService, ClientChatMessage} from "../services/ChatService.ts";
3+
import {ApiChatMessage, ChatService, ClientChatMessage} from "../../services/ChatService.ts";
4+
import {ChatPayload} from "../../types/chat.ts";
5+
import {Message} from "../Message/Message.tsx";
6+
import {ChatInput} from "../ChatInput/ChatInput.tsx";
7+
import {useNotification} from "../../context/useNotification.ts";
88

99
interface ModelSettings {
1010
temperature: number;

src/components/ChatInput.tsx renamed to src/components/ChatInput/ChatInput.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, {KeyboardEvent, useState} from 'react';
2-
import {ToolsToggle} from './ChatInputButton/ToolsToggle.tsx';
3-
import {LLMProviderToggle} from "./LLMProviderToggle.tsx";
2+
import {ToolsToggle} from "../ChatInputButton/ToolsToggle.tsx";
3+
import {LLMProviderToggle} from "../LLMProviderToggle/LLMProviderToggle.tsx";
44

55
interface ChatInputProps {
66
onSubmit: (message: string) => Promise<void>;
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { render, screen, fireEvent } from '@testing-library/react';
2+
import '@testing-library/jest-dom';
3+
import {fetchTools} from "../../api";
4+
import {act} from "react";
5+
import {ToolsModal} from "./ToolsModal.tsx";
6+
import {Tool} from "../../types/tools.ts";
7+
8+
// Mock the entire api module
9+
jest.mock('../../api', () => ({
10+
fetchTools: jest.fn()
11+
}));
12+
13+
// Mock the XMarkIcon component
14+
jest.mock('@heroicons/react/24/outline', () => ({
15+
XMarkIcon: () => <div data-testid="close-icon">X</div>
16+
}));
17+
18+
// Mock SearchBar component
19+
jest.mock('../SearchBar.tsx', () => ({
20+
SearchBar: ({ value, onChange }: { value: string; onChange: (value: string) => void }) => (
21+
<input
22+
data-testid="search-bar"
23+
value={value}
24+
onChange={(e) => onChange(e.target.value)}
25+
placeholder="Search tools"
26+
/>
27+
)
28+
}));
29+
30+
// Mock ToolItem component
31+
jest.mock('../ToolItem/ToolItem', () => ({
32+
ToolItem: ({ tool, isSelected, onToggle }: {
33+
tool: Tool;
34+
isSelected: boolean;
35+
onToggle: (name: string) => void
36+
}) => (
37+
<div
38+
data-testid={`tool-item-${tool.name}`}
39+
onClick={() => onToggle(tool.name)}
40+
>
41+
{tool.name} {isSelected ? '(Selected)' : ''}
42+
</div>
43+
)
44+
}));
45+
46+
const mockTools = [
47+
{ name: 'Tool1', description: 'Description 1' },
48+
{ name: 'Tool2', description: 'Description 2' },
49+
{ name: 'Tool3', description: 'Description 3' },
50+
];
51+
52+
describe('ToolsModal', () => {
53+
const mockOnClose = jest.fn();
54+
const mockOnSave = jest.fn();
55+
56+
beforeEach(() => {
57+
jest.clearAllMocks();
58+
(fetchTools as jest.Mock).mockResolvedValue(mockTools);
59+
});
60+
61+
it('should render and fetch tools when opened', async () => {
62+
await act(async () => {
63+
render(
64+
<ToolsModal
65+
isOpen={true}
66+
onClose={mockOnClose}
67+
onSave={mockOnSave}
68+
initialSelectedTools={[]}
69+
/>
70+
);
71+
});
72+
73+
expect(screen.getByText('Available Tools')).toBeInTheDocument();
74+
expect(fetchTools).toHaveBeenCalledTimes(1);
75+
76+
expect(screen.getByTestId('tool-item-Tool1')).toBeInTheDocument();
77+
expect(screen.getByTestId('tool-item-Tool2')).toBeInTheDocument();
78+
expect(screen.getByTestId('tool-item-Tool3')).toBeInTheDocument();
79+
});
80+
81+
it('should filter tools based on search query', async () => {
82+
await act(async () => {
83+
render(
84+
<ToolsModal
85+
isOpen={true}
86+
onClose={mockOnClose}
87+
onSave={mockOnSave}
88+
initialSelectedTools={[]}
89+
/>
90+
);
91+
});
92+
93+
const searchInput = screen.getByTestId('search-bar');
94+
await act(async () => {
95+
fireEvent.change(searchInput, { target: { value: 'Tool1' } });
96+
});
97+
98+
expect(screen.getByTestId('tool-item-Tool1')).toBeInTheDocument();
99+
expect(screen.queryByTestId('tool-item-Tool2')).not.toBeInTheDocument();
100+
});
101+
102+
it('should handle tool selection and deselection', async () => {
103+
await act(async () => {
104+
render(
105+
<ToolsModal
106+
isOpen={true}
107+
onClose={mockOnClose}
108+
onSave={mockOnSave}
109+
initialSelectedTools={[]}
110+
/>
111+
);
112+
});
113+
114+
await act(async () => {
115+
fireEvent.click(screen.getByTestId('tool-item-Tool1'));
116+
});
117+
118+
await act(async () => {
119+
fireEvent.click(screen.getByText('Save'));
120+
});
121+
122+
expect(mockOnSave).toHaveBeenCalledWith(['Tool1']);
123+
expect(mockOnClose).toHaveBeenCalled();
124+
});
125+
126+
it('should close without saving when clicking cancel', async () => {
127+
await act(async () => {
128+
render(
129+
<ToolsModal
130+
isOpen={true}
131+
onClose={mockOnClose}
132+
onSave={mockOnSave}
133+
initialSelectedTools={[]}
134+
/>
135+
);
136+
});
137+
138+
await act(async () => {
139+
fireEvent.click(screen.getByText('Cancel'));
140+
});
141+
142+
expect(mockOnSave).not.toHaveBeenCalled();
143+
expect(mockOnClose).toHaveBeenCalled();
144+
});
145+
146+
it('should initialize with provided selected tools', async () => {
147+
await act(async () => {
148+
render(
149+
<ToolsModal
150+
isOpen={true}
151+
onClose={mockOnClose}
152+
onSave={mockOnSave}
153+
initialSelectedTools={['Tool1']}
154+
/>
155+
);
156+
});
157+
158+
expect(screen.getByTestId('tool-item-Tool1').textContent).toContain('(Selected)');
159+
});
160+
});

src/components/ChatInputButton/ToolsModal.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
// components/ToolsModal.tsx
21
import React, {useEffect, useMemo, useState} from 'react';
32
import {XMarkIcon} from "@heroicons/react/24/outline";
43
import {Tool, ToolsModalProps} from "../../types/tools.ts";
54
import {SearchBar} from "../SearchBar.tsx";
6-
import {ToolItem} from "../ToolItem.tsx";
75
import {fetchTools} from "../../api";
6+
import {ToolItem} from "../ToolItem/ToolItem.tsx";
87

98
export const ToolsModal: React.FC<ToolsModalProps> = ({
109
isOpen,
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { render, screen, fireEvent } from '@testing-library/react';
2+
import '@testing-library/jest-dom';
3+
import {ToolsToggle} from "./ToolsToggle.tsx";
4+
5+
// Define the interface for ToolsModal props
6+
interface MockToolsModalProps {
7+
isOpen: boolean;
8+
onClose: () => void;
9+
onSave: (tools: string[]) => void;
10+
initialSelectedTools: string[];
11+
}
12+
13+
// Mock the ToolsModal component with proper typing
14+
jest.mock('./ToolsModal', () => ({
15+
ToolsModal: ({ isOpen, onClose, onSave, initialSelectedTools }: MockToolsModalProps) =>
16+
isOpen ? (
17+
<div data-testid="tools-modal">
18+
<button onClick={() => {
19+
onSave(initialSelectedTools);
20+
onClose(); // Make sure we call onClose after onSave
21+
}}>Save</button>
22+
<button onClick={onClose}>Close</button>
23+
</div>
24+
) : null
25+
}));
26+
27+
28+
29+
describe('ToolsToggle', () => {
30+
const mockOnToolsChange = jest.fn();
31+
32+
beforeEach(() => {
33+
jest.clearAllMocks();
34+
});
35+
36+
37+
it('renders with default state (no tools selected)', () => {
38+
render(<ToolsToggle selectedTools={[]} onToolsChange={mockOnToolsChange} />);
39+
40+
const button = screen.getByRole('button');
41+
expect(button).toHaveTextContent('Tools Disabled');
42+
expect(button).toHaveClass('bg-gray-200');
43+
});
44+
45+
it('renders with selected tools count', () => {
46+
render(
47+
<ToolsToggle
48+
selectedTools={['tool1', 'tool2']}
49+
onToolsChange={mockOnToolsChange}
50+
/>
51+
);
52+
53+
const button = screen.getByRole('button');
54+
expect(button).toHaveTextContent('Tools (2)');
55+
expect(button).toHaveClass('bg-gray-800');
56+
});
57+
58+
it('opens modal when clicked', () => {
59+
render(<ToolsToggle selectedTools={[]} onToolsChange={mockOnToolsChange} />);
60+
61+
const button = screen.getByRole('button');
62+
fireEvent.click(button);
63+
64+
expect(screen.getByTestId('tools-modal')).toBeInTheDocument();
65+
});
66+
67+
it('closes modal and calls onToolsChange when saving', () => {
68+
const selectedTools = ['tool1', 'tool2'];
69+
render(
70+
<ToolsToggle
71+
selectedTools={selectedTools}
72+
onToolsChange={mockOnToolsChange}
73+
/>
74+
);
75+
76+
// Open modal
77+
fireEvent.click(screen.getByRole('button'));
78+
79+
// Click save in modal
80+
fireEvent.click(screen.getByText('Save'));
81+
82+
expect(mockOnToolsChange).toHaveBeenCalledWith(selectedTools);
83+
expect(screen.queryByTestId('tools-modal')).not.toBeInTheDocument();
84+
});
85+
86+
it('closes modal without calling onToolsChange when clicking close', () => {
87+
render(
88+
<ToolsToggle
89+
selectedTools={[]}
90+
onToolsChange={mockOnToolsChange}
91+
/>
92+
);
93+
94+
// Open modal
95+
fireEvent.click(screen.getByRole('button'));
96+
97+
// Click close in modal
98+
fireEvent.click(screen.getByText('Close'));
99+
100+
expect(mockOnToolsChange).not.toHaveBeenCalled();
101+
expect(screen.queryByTestId('tools-modal')).not.toBeInTheDocument();
102+
});
103+
});

src/components/LLMProviderCard.tsx renamed to src/components/LLMProviderModal/LLMProviderCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import {Provider} from '../types/llm';
2+
import {Provider} from '../../types/llm';
33

44
interface LLMProviderCardProps {
55
provider: Provider;

src/components/LLMProvidersModal.tsx renamed to src/components/LLMProviderModal/LLMProvidersModal.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import React, {useEffect, useState} from 'react';
22
import {XMarkIcon} from "@heroicons/react/24/outline";
3-
import {LLMProvidersModalProps, Provider} from '../types/llm';
3+
import {LLMProvidersModalProps, Provider} from '../../types/llm';
44
import {LLMProviderCard} from './LLMProviderCard';
55
import {useAuth0} from "@auth0/auth0-react";
6-
import {LLMService} from "../services/LLMService";
6+
import {LLMService} from "../../services/LLMService.ts";
77

88
export const LLMProvidersModal: React.FC<LLMProvidersModalProps> = ({
99
isOpen,

src/components/LLMProviderToggle.tsx renamed to src/components/LLMProviderToggle/LLMProviderToggle.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// components/LLMProviderToggle.tsx
22
import React, {useState} from 'react';
33
import {CircleStackIcon} from "@heroicons/react/24/outline";
4-
import {LLMProvidersModal} from './LLMProvidersModal';
4+
import {LLMProvidersModal} from "../LLMProviderModal/LLMProvidersModal.tsx";
55

66
interface LLMProviderToggleProps {
77
selectedProvider: string | null;

0 commit comments

Comments
 (0)