> = {}) => {
+ const changeHandler = props.changeHandler ?? jest.fn();
+ render(
+
+
+
+ );
+ return { changeHandler };
+};
+
+describe('CustomTextField', () => {
+ it('renders labeled text input', () => {
+ renderField({ label: 'Email', name: 'email' });
+ expect(screen.getByLabelText('Email')).toBeInTheDocument();
+ });
+
+ it('calls changeHandler when user types', async () => {
+ const user = userEvent.setup();
+ const { changeHandler } = renderField();
+
+ await user.type(screen.getByLabelText('Username'), 'abc');
+ expect(changeHandler).toHaveBeenCalled();
+ });
+
+ it('supports multiline mode', () => {
+ renderField({ label: 'Notes', name: 'notes', isMultline: true });
+ expect(screen.getByLabelText('Notes').tagName).toBe('TEXTAREA');
+ });
+});
diff --git a/llm-fe/src/llm-fe/components/CustomToastMessage/CustomToastMessage.test.tsx b/llm-fe/src/llm-fe/components/CustomToastMessage/CustomToastMessage.test.tsx
new file mode 100644
index 0000000..94106ab
--- /dev/null
+++ b/llm-fe/src/llm-fe/components/CustomToastMessage/CustomToastMessage.test.tsx
@@ -0,0 +1,10 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import CustomToastMessage from './CustomeToastMessage';
+
+describe('CustomToastMessage', () => {
+ it('renders the provided message in toast body', () => {
+ render();
+ expect(screen.getByText('Saved successfully')).toBeInTheDocument();
+ });
+});
diff --git a/llm-fe/src/llm-fe/components/Footer/Footer.test.tsx b/llm-fe/src/llm-fe/components/Footer/Footer.test.tsx
new file mode 100644
index 0000000..f510cf9
--- /dev/null
+++ b/llm-fe/src/llm-fe/components/Footer/Footer.test.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import Footer from './Footer';
+
+jest.mock('../../ui-kit/components/MDTypography', () => ({
+ __esModule: true,
+ default: ({ children }: { children: React.ReactNode }) => {children}
,
+}));
+
+describe('Footer', () => {
+ it('renders copyright and developer credit', () => {
+ render();
+
+ expect(screen.getByText(/© 2025 Chat/i)).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: /AI ML Operations, LLC/i })).toHaveAttribute(
+ 'href',
+ 'www.aimloperations.com'
+ );
+ });
+});
diff --git a/llm-fe/src/llm-fe/contexts/AuthContext.test.tsx b/llm-fe/src/llm-fe/contexts/AuthContext.test.tsx
new file mode 100644
index 0000000..1a171b3
--- /dev/null
+++ b/llm-fe/src/llm-fe/contexts/AuthContext.test.tsx
@@ -0,0 +1,71 @@
+import React, { useContext } from 'react';
+import { render, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { MemoryRouter } from 'react-router-dom';
+import { AuthContext, AuthProvider } from './AuthContext';
+
+const Probe = () => {
+ const { authenticated, loading, setAuthentication } = useContext(AuthContext);
+
+ if (loading) {
+ return loading
;
+ }
+
+ return (
+
+ {authenticated ? 'yes' : 'no'}
+
+
+ );
+};
+
+const renderWithProvider = () =>
+ render(
+
+
+
+
+
+ );
+
+/** Minimal JWT payload with exp far in the future (year 2286). */
+const FUTURE_TOKEN =
+ 'eyJhbGciOiJub25lIn0.eyJleHAiOjE5OTk5OTk5OTk5fQ.';
+
+describe('AuthProvider', () => {
+ beforeEach(() => {
+ localStorage.clear();
+ });
+
+ it('starts unauthenticated when no token stored', async () => {
+ renderWithProvider();
+
+ await waitFor(() => {
+ expect(screen.queryByText('loading')).not.toBeInTheDocument();
+ });
+ expect(screen.getByTestId('auth-state')).toHaveTextContent('no');
+ });
+
+ it('authenticates when valid access token present', async () => {
+ localStorage.setItem('access_token', FUTURE_TOKEN);
+ renderWithProvider();
+
+ await waitFor(() => {
+ expect(screen.getByTestId('auth-state')).toHaveTextContent('yes');
+ });
+ });
+
+ it('updates authenticated state via setAuthentication', async () => {
+ const user = userEvent.setup();
+ renderWithProvider();
+
+ await waitFor(() => {
+ expect(screen.getByTestId('auth-state')).toHaveTextContent('no');
+ });
+
+ await user.click(screen.getByRole('button', { name: 'login' }));
+ expect(screen.getByTestId('auth-state')).toHaveTextContent('yes');
+ });
+});
diff --git a/llm-fe/src/llm-fe/data.test.ts b/llm-fe/src/llm-fe/data.test.ts
new file mode 100644
index 0000000..cfe8ff1
--- /dev/null
+++ b/llm-fe/src/llm-fe/data.test.ts
@@ -0,0 +1,195 @@
+import {
+ Account,
+ Announcement,
+ Company,
+ Conversation,
+ ConversationPrompt,
+ Document,
+ Feedback,
+} from './data';
+
+describe('ConversationPrompt', () => {
+ it('uses defaults when constructed with no args', () => {
+ const prompt = new ConversationPrompt();
+ expect(prompt.id).toBeUndefined();
+ expect(prompt.message).toBe('');
+ expect(prompt.user_created).toBe(false);
+ expect(prompt.created_timestamp).toBeInstanceOf(Date);
+ });
+
+ it('hydrates from initializer', () => {
+ const created = new Date('2024-01-01');
+ const prompt = new ConversationPrompt({
+ id: 7,
+ message: 'hello',
+ user_created: true,
+ created_timestamp: created,
+ });
+ expect(prompt.id).toBe(7);
+ expect(prompt.message).toBe('hello');
+ expect(prompt.user_created).toBe(true);
+ expect(prompt.created_timestamp).toEqual(created);
+ });
+});
+
+describe('Company', () => {
+ it('uses defaults when constructed with no args', () => {
+ const company = new Company();
+ expect(company.id).toBeUndefined();
+ expect(company.name).toBe('');
+ expect(company.state).toBe('');
+ expect(company.zipcode).toBeUndefined();
+ expect(company.address).toBe('');
+ });
+
+ it('hydrates from initializer', () => {
+ const company = new Company({
+ id: 1,
+ name: 'Acme',
+ state: 'CA',
+ zipcode: 90210,
+ address: '1 Main St',
+ });
+ expect(company.id).toBe(1);
+ expect(company.name).toBe('Acme');
+ expect(company.state).toBe('CA');
+ expect(company.zipcode).toBe(90210);
+ expect(company.address).toBe('1 Main St');
+ });
+});
+
+describe('Conversation', () => {
+ it('uses defaults when constructed with no args', () => {
+ const conversation = new Conversation();
+ expect(conversation.id).toBeUndefined();
+ expect(conversation.title).toBe('');
+ expect(conversation.conversationDetail).toEqual([]);
+ expect(conversation.account).toBeUndefined();
+ });
+
+ it('hydrates from initializer', () => {
+ const detail = [new ConversationPrompt({ id: 1, message: 'hi' })];
+ const account = new Account({ email: 'a@b.com' });
+ const conversation = new Conversation({
+ id: 42,
+ title: 'Chat',
+ conversationDetail: detail,
+ account,
+ });
+ expect(conversation.id).toBe(42);
+ expect(conversation.title).toBe('Chat');
+ expect(conversation.conversationDetail).toEqual(detail);
+ expect(conversation.account).toEqual(account);
+ });
+});
+
+describe('Announcement', () => {
+ it('uses defaults when constructed with no args', () => {
+ const announcement = new Announcement();
+ expect(announcement.status).toBe('default');
+ expect(announcement.message).toBe('');
+ });
+
+ it('hydrates from initializer', () => {
+ const announcement = new Announcement({
+ status: 'info',
+ message: 'Welcome',
+ });
+ expect(announcement.status).toBe('info');
+ expect(announcement.message).toBe('Welcome');
+ });
+});
+
+describe('Document', () => {
+ it('uses defaults when constructed with no args', () => {
+ const doc = new Document();
+ expect(doc.id).toBe(0);
+ expect(doc.name).toBe('');
+ expect(doc.file).toBe('');
+ expect(doc.date_uploaded).toBe('');
+ expect(doc.active).toBe(false);
+ expect(doc.processed).toBe(false);
+ });
+
+ it('hydrates from initializer', () => {
+ const doc = new Document({
+ id: 9,
+ name: 'report.pdf',
+ file: '/files/report.pdf',
+ date_uploaded: '2024-06-01',
+ active: true,
+ processed: true,
+ });
+ expect(doc.id).toBe(9);
+ expect(doc.name).toBe('report.pdf');
+ expect(doc.file).toBe('/files/report.pdf');
+ expect(doc.date_uploaded).toBe('2024-06-01');
+ expect(doc.active).toBe(true);
+ expect(doc.processed).toBe(true);
+ });
+});
+
+describe('Feedback', () => {
+ it('uses defaults when constructed with no args', () => {
+ const feedback = new Feedback();
+ expect(feedback.id).toBe(0);
+ expect(feedback.title).toBe('');
+ expect(feedback.status).toBe('');
+ expect(feedback.text).toBe('');
+ expect(feedback.category).toBe('');
+ });
+
+ it('hydrates from initializer', () => {
+ const feedback = new Feedback({
+ id: 3,
+ title: 'Bug',
+ status: 'open',
+ text: 'Broken button',
+ category: 'ui',
+ });
+ expect(feedback.id).toBe(3);
+ expect(feedback.title).toBe('Bug');
+ expect(feedback.status).toBe('open');
+ expect(feedback.text).toBe('Broken button');
+ expect(feedback.category).toBe('ui');
+ });
+});
+
+describe('Account', () => {
+ it('uses defaults when constructed with no args', () => {
+ const account = new Account();
+ expect(account.email).toBe('');
+ expect(account.first_name).toBe('');
+ expect(account.last_name).toBe('');
+ expect(account.role).toBeUndefined();
+ expect(account.company).toBeUndefined();
+ expect(account.is_company_manager).toBe(false);
+ expect(account.has_password).toBe(false);
+ expect(account.is_active).toBe(false);
+ expect(account.has_signed_tos).toBe(false);
+ });
+
+ it('hydrates from initializer', () => {
+ const company = new Company({ id: 1, name: 'Acme' });
+ const account = new Account({
+ email: 'user@example.com',
+ first_name: 'Jane',
+ last_name: 'Doe',
+ role: 'admin',
+ company,
+ is_company_manager: true,
+ has_password: true,
+ is_active: true,
+ has_signed_tos: true,
+ });
+ expect(account.email).toBe('user@example.com');
+ expect(account.first_name).toBe('Jane');
+ expect(account.last_name).toBe('Doe');
+ expect(account.role).toBe('admin');
+ expect(account.company).toEqual(company);
+ expect(account.is_company_manager).toBe(true);
+ expect(account.has_password).toBe(true);
+ expect(account.is_active).toBe(true);
+ expect(account.has_signed_tos).toBe(true);
+ });
+});
diff --git a/llm-fe/src/llm-fe/pages/SignIn/SignIn.test.tsx b/llm-fe/src/llm-fe/pages/SignIn/SignIn.test.tsx
new file mode 100644
index 0000000..ae36c6e
--- /dev/null
+++ b/llm-fe/src/llm-fe/pages/SignIn/SignIn.test.tsx
@@ -0,0 +1,112 @@
+import React from 'react';
+import { render, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { MemoryRouter } from 'react-router-dom';
+import SignIn from './SignIn';
+import { AuthContext } from '../../contexts/AuthContext';
+import { AccountContext } from '../../contexts/AccountContext';
+
+jest.mock('../../components/ParticleBackground/ParticleBackground', () => () => null);
+
+const mockPost = jest.fn();
+const mockGet = jest.fn();
+
+jest.mock('../../../axiosApi', () => ({
+ axiosInstance: {
+ post: (...args: unknown[]) => mockPost(...args),
+ get: (...args: unknown[]) => mockGet(...args),
+ defaults: { headers: {} as Record },
+ },
+}));
+
+const renderSignIn = () => {
+ const setAuthentication = jest.fn();
+ const setNeedsNewPassword = jest.fn();
+ const setAccount = jest.fn();
+
+ render(
+
+
+
+
+
+
+
+ );
+
+ return { setAuthentication, setNeedsNewPassword, setAccount };
+};
+
+describe('SignIn', () => {
+ beforeEach(() => {
+ localStorage.clear();
+ mockPost.mockReset();
+ mockGet.mockReset();
+ });
+
+ it('renders sign-in form fields', () => {
+ renderSignIn();
+
+ expect(screen.getByRole('heading', { name: 'Sign In' })).toBeInTheDocument();
+ expect(screen.getByPlaceholderText('Email Address')).toBeInTheDocument();
+ expect(screen.getByPlaceholderText('Password')).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Sign In' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Forgot Password?' })).toBeInTheDocument();
+ });
+
+ it('does not submit when required fields empty', async () => {
+ const user = userEvent.setup();
+ renderSignIn();
+
+ await user.click(screen.getByRole('button', { name: 'Sign In' }));
+
+ await waitFor(() => {
+ expect(mockPost).not.toHaveBeenCalled();
+ });
+ });
+
+ it('authenticates on successful submit', async () => {
+ const user = userEvent.setup();
+ mockPost.mockResolvedValue({
+ data: { access: 'access-token', refresh: 'refresh-token' },
+ });
+ mockGet.mockResolvedValue({
+ data: {
+ email: 'user@example.com',
+ first_name: 'Jane',
+ last_name: 'Doe',
+ is_company_manager: false,
+ has_signed_tos: true,
+ has_usable_password: true,
+ company: { id: 1, name: 'Acme', state: 'CA', zipcode: '90001', address: '1 Main' },
+ },
+ });
+
+ const { setAuthentication, setAccount } = renderSignIn();
+
+ await user.type(screen.getByPlaceholderText('Email Address'), 'user@example.com');
+ await user.type(screen.getByPlaceholderText('Password'), 'secret123');
+ await user.click(screen.getByRole('button', { name: 'Sign In' }));
+
+ await waitFor(() => {
+ expect(mockPost).toHaveBeenCalledWith('/token/obtain/', {
+ username: 'user@example.com',
+ password: 'secret123',
+ });
+ });
+ await waitFor(() => {
+ expect(setAuthentication).toHaveBeenCalledWith(true);
+ expect(setAccount).toHaveBeenCalled();
+ expect(localStorage.getItem('access_token')).toBe('access-token');
+ expect(localStorage.getItem('refresh_token')).toBe('refresh-token');
+ });
+ });
+});