fix(tests): resolve quite a few broen tests

This commit is contained in:
perf3ct 2025-07-05 18:12:48 +00:00
parent 7fb1116695
commit 8d181146a2
No known key found for this signature in database
GPG Key ID: 569C4EEC436F5232
21 changed files with 560 additions and 329 deletions

View File

@ -1,7 +1,23 @@
import { screen, fireEvent, waitFor } from '@testing-library/react';
import { vi } from 'vitest';
import Login from '../Login';
import { renderWithProviders, setupTestEnvironment } from '../../../test/test-utils';
// Mock AuthContext to work with the test setup
vi.mock('../../../contexts/AuthContext', () => ({
useAuth: vi.fn(() => ({
user: null,
loading: false,
login: vi.fn().mockResolvedValue({}),
register: vi.fn().mockResolvedValue({}),
logout: vi.fn(),
})),
}));
// Mock ThemeContext
vi.mock('../../../contexts/ThemeContext', () => ({
useTheme: () => ({
darkMode: false,
toggleDarkMode: vi.fn()
}),
}));
// Mock the API
vi.mock('../../../services/api', () => ({
@ -25,6 +41,11 @@ vi.mock('react-router-dom', async () => {
};
});
// Now import after all mocks are set up
import { screen, fireEvent, waitFor } from '@testing-library/react';
import { renderWithProviders, createMockUser } from '../../../test/test-utils';
import Login from '../Login';
// Mock window.location
Object.defineProperty(window, 'location', {
value: {
@ -36,7 +57,6 @@ Object.defineProperty(window, 'location', {
describe('Login - OIDC Features', () => {
beforeEach(() => {
vi.clearAllMocks();
setupTestEnvironment();
});
const renderLogin = () => {

View File

@ -1,20 +1,46 @@
import { screen, waitFor, fireEvent } from '@testing-library/react';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
import { vi } from 'vitest';
import OidcCallback from '../OidcCallback';
import { renderWithProviders, setupTestEnvironment } from '../../../test/test-utils';
import { api } from '../../../services/api';
import React from 'react';
// Mock the API
vi.mock('../../../services/api', () => ({
api: {
get: vi.fn(),
defaults: {
headers: {
common: {}
}
// Create stable mock functions
const mockLogin = vi.fn().mockResolvedValue({});
const mockRegister = vi.fn().mockResolvedValue({});
const mockLogout = vi.fn();
// Mock the auth context module completely
vi.mock('../../../contexts/AuthContext', () => ({
useAuth: vi.fn(() => ({
user: null,
loading: false,
login: mockLogin,
register: mockRegister,
logout: mockLogout,
})),
AuthProvider: ({ children }: { children: React.ReactNode }) => React.createElement('div', null, children),
}));
// Mock axios comprehensively to prevent any real HTTP requests
import { createComprehensiveAxiosMock, createComprehensiveApiMocks } from '../../../test/comprehensive-mocks';
vi.mock('axios', () => createComprehensiveAxiosMock());
// Create the mock API object
const mockApi = {
get: vi.fn().mockResolvedValue({ data: { token: 'default-token' } }),
post: vi.fn().mockResolvedValue({ data: { success: true } }),
put: vi.fn().mockResolvedValue({ data: { success: true } }),
delete: vi.fn().mockResolvedValue({ data: { success: true } }),
patch: vi.fn().mockResolvedValue({ data: { success: true } }),
defaults: {
headers: {
common: {}
}
}
};
// Mock the services/api file
vi.mock('../../../services/api', () => ({
api: mockApi,
default: mockApi,
}));
// Mock useNavigate
@ -28,6 +54,11 @@ vi.mock('react-router-dom', async () => {
};
});
// Now import after mocks
import { screen, waitFor, fireEvent } from '@testing-library/react';
import { renderWithProviders } from '../../../test/test-utils';
import OidcCallback from '../OidcCallback';
// Mock window.location
Object.defineProperty(window, 'location', {
value: {
@ -39,28 +70,29 @@ Object.defineProperty(window, 'location', {
describe('OidcCallback', () => {
beforeEach(() => {
vi.clearAllMocks();
setupTestEnvironment();
vi.resetModules();
window.location.href = '';
// Clear API mocks
(api.get as any).mockClear();
mockApi.get.mockClear();
// Reset API mocks to default implementation
(api.get as any).mockResolvedValue({ data: { token: 'default-token' } });
mockApi.get.mockResolvedValue({ data: { token: 'default-token' } });
});
const renderOidcCallback = (search = '') => {
return renderWithProviders(
<MemoryRouter initialEntries={[`/auth/oidc/callback${search}`]}>
<Routes>
<Route path="/auth/oidc/callback" element={<OidcCallback />} />
<Route path="/login" element={<div>Login Page</div>} />
</Routes>
</MemoryRouter>
);
// Mock the URL search params for the component
const url = new URL(`http://localhost/auth/oidc/callback${search}`);
Object.defineProperty(window, 'location', {
value: { search: url.search },
writable: true
});
// Use renderWithProviders to get auth context
return renderWithProviders(<OidcCallback />);
};
it('shows loading state initially', async () => {
// Mock the API call to delay so we can see the loading state
(api.get as any).mockImplementation(() => new Promise(() => {})); // Never resolves
mockApi.get.mockImplementation(() => new Promise(() => {})); // Never resolves
renderOidcCallback('?code=test-code&state=test-state');
@ -80,12 +112,12 @@ describe('OidcCallback', () => {
}
};
(api.get as any).mockResolvedValueOnce(mockResponse);
mockApi.get.mockResolvedValueOnce(mockResponse);
renderOidcCallback('?code=test-code&state=test-state');
await waitFor(() => {
expect(api.get).toHaveBeenCalledWith('/auth/oidc/callback?code=test-code&state=test-state');
expect(mockApi.get).toHaveBeenCalledWith('/auth/oidc/callback?code=test-code&state=test-state');
});
expect(localStorage.setItem).toHaveBeenCalledWith('token', 'test-jwt-token');
@ -114,7 +146,7 @@ describe('OidcCallback', () => {
}
}
};
(api.get as any).mockRejectedValueOnce(error);
mockApi.get.mockRejectedValueOnce(error);
renderOidcCallback('?code=test-code&state=test-state');
@ -125,7 +157,7 @@ describe('OidcCallback', () => {
});
it('handles invalid response from server', async () => {
(api.get as any).mockResolvedValueOnce({
mockApi.get.mockResolvedValueOnce({
data: {
// Missing token
user: { id: '123' }
@ -141,7 +173,7 @@ describe('OidcCallback', () => {
});
it('provides return to login button on error', async () => {
(api.get as any).mockRejectedValueOnce(new Error('Network error'));
mockApi.get.mockRejectedValueOnce(new Error('Network error'));
renderOidcCallback('?code=test-code&state=test-state');

View File

@ -1,14 +1,19 @@
import React from 'react';
import { screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { vi } from 'vitest';
import GlobalSearchBar from '../GlobalSearchBar';
import { renderWithProviders, createMockApiServices, setupTestEnvironment, createMockLocalStorage } from '../../../test/test-utils';
import { createComprehensiveAxiosMock, createComprehensiveApiMocks } from '../../../test/comprehensive-mocks';
// Use centralized API mocking
const mockServices = createMockApiServices();
const mockDocumentService = mockServices.documentService;
const localStorageMock = createMockLocalStorage();
// Mock axios comprehensively to prevent any real HTTP requests
vi.mock('axios', () => createComprehensiveAxiosMock());
// Mock API services comprehensively
vi.mock('../../../services/api', async () => {
const actual = await vi.importActual('../../../services/api');
const apiMocks = createComprehensiveApiMocks();
return {
...actual,
...apiMocks,
};
});
// Mock useNavigate
const mockNavigate = vi.fn();
@ -20,6 +25,18 @@ vi.mock('react-router-dom', async () => {
};
});
// Import after mocking
import React from 'react';
import { screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import GlobalSearchBar from '../GlobalSearchBar';
import { renderWithProviders, createMockApiServices, createMockLocalStorage } from '../../../test/test-utils';
// Use centralized API mocking
const mockServices = createMockApiServices();
const mockDocumentService = mockServices.documentService;
const localStorageMock = createMockLocalStorage();
// Mock data
const mockSearchResponse = {
data: {
@ -45,7 +62,6 @@ const mockSearchResponse = {
describe('GlobalSearchBar', () => {
beforeEach(() => {
vi.clearAllMocks();
setupTestEnvironment();
localStorageMock.getItem.mockReturnValue(null);
mockDocumentService.enhancedSearch.mockResolvedValue(mockSearchResponse);
});

View File

@ -4,8 +4,7 @@ import Label, { type LabelData } from '../Label';
import { renderWithProviders } from '../../../test/test-utils';
import {
createMockLabel,
createMockSystemLabel,
setupTestEnvironment
createMockSystemLabel
} from '../../../test/label-test-utils';
const mockLabel = createMockLabel({
@ -31,7 +30,7 @@ const renderLabel = (props: Partial<React.ComponentProps<typeof Label>> = {}) =>
describe('Label Component', () => {
beforeEach(() => {
setupTestEnvironment();
// Test setup is handled globally
});
describe('Basic Rendering', () => {

View File

@ -1,15 +1,17 @@
import { describe, test, expect, vi, beforeEach } from 'vitest';
import { screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import LabelCreateDialog from '../LabelCreateDialog';
import { type LabelData } from '../Label';
import { renderWithProviders } from '../../../test/test-utils';
import {
createMockLabel,
setupTestEnvironment,
labelValidationScenarios
} from '../../../test/label-test-utils';
const theme = createTheme();
const mockEditingLabel = createMockLabel({
name: 'Existing Label',
description: 'An existing label',
@ -33,7 +35,6 @@ describe('LabelCreateDialog Component', () => {
let user: ReturnType<typeof userEvent.setup>;
beforeEach(() => {
setupTestEnvironment();
user = userEvent.setup();
});

View File

@ -5,7 +5,6 @@ import LabelSelector from '../LabelSelector';
import { type LabelData } from '../Label';
import { renderWithProviders } from '../../../test/test-utils';
import {
setupTestEnvironment,
testDataBuilders
} from '../../../test/label-test-utils';
@ -26,7 +25,6 @@ describe('LabelSelector Component', () => {
let user: ReturnType<typeof userEvent.setup>;
beforeEach(() => {
setupTestEnvironment();
user = userEvent.setup();
});
@ -81,7 +79,7 @@ describe('LabelSelector Component', () => {
await waitFor(() => {
expect(screen.getByText('Work')).toBeInTheDocument();
expect(screen.getByText('Personal Project')).toBeInTheDocument();
expect(screen.getByText('Project Alpha')).toBeInTheDocument();
});
// Important should not appear in the dropdown options (but may appear in selected tags)
@ -141,7 +139,7 @@ describe('LabelSelector Component', () => {
test('should remove label when delete button is clicked', async () => {
const onLabelsChange = vi.fn();
// Use only non-system labels since system labels don't have delete buttons
const selectedLabels = [mockLabels[2]]; // Personal Project (non-system)
const selectedLabels = [mockLabels[3]]; // Project Alpha (non-system)
renderLabelSelector({
selectedLabels,
@ -149,7 +147,7 @@ describe('LabelSelector Component', () => {
});
// Find the chip with the delete button
const personalProjectChip = screen.getByText('Personal Project').closest('.MuiChip-root');
const personalProjectChip = screen.getByText('Project Alpha').closest('.MuiChip-root');
expect(personalProjectChip).toBeInTheDocument();
// Find the delete button within that specific chip
@ -164,7 +162,7 @@ describe('LabelSelector Component', () => {
});
test('should not show delete buttons when disabled', () => {
const selectedLabels = [mockLabels[2]]; // Non-system label
const selectedLabels = [mockLabels[3]]; // Non-system label
renderLabelSelector({
selectedLabels,
@ -186,7 +184,7 @@ describe('LabelSelector Component', () => {
// Check that labels appear in the dropdown
expect(screen.getByText('Important')).toBeInTheDocument();
expect(screen.getByText('Work')).toBeInTheDocument();
expect(screen.getByText('Personal Project')).toBeInTheDocument();
expect(screen.getByText('Project Alpha')).toBeInTheDocument();
});
});
@ -200,7 +198,7 @@ describe('LabelSelector Component', () => {
await waitFor(() => {
expect(screen.getByText('Important')).toBeInTheDocument();
expect(screen.getByText('Work')).toBeInTheDocument();
expect(screen.queryByText('Personal Project')).not.toBeInTheDocument();
expect(screen.queryByText('Project Alpha')).not.toBeInTheDocument();
});
});
});
@ -215,7 +213,7 @@ describe('LabelSelector Component', () => {
await waitFor(() => {
expect(screen.getByText('Work')).toBeInTheDocument();
expect(screen.queryByText('Important')).not.toBeInTheDocument();
expect(screen.queryByText('Personal Project')).not.toBeInTheDocument();
expect(screen.queryByText('Project Alpha')).not.toBeInTheDocument();
});
});
@ -376,10 +374,10 @@ describe('LabelSelector Component', () => {
await user.click(input);
await waitFor(() => {
expect(screen.getByText('Personal Project')).toBeInTheDocument();
expect(screen.getByText('Project Alpha')).toBeInTheDocument();
});
await user.click(screen.getByText('Personal Project'));
await user.click(screen.getByText('Project Alpha'));
// Should not add the third label due to maxTags limit
expect(onLabelsChange).not.toHaveBeenCalled();

View File

@ -1,8 +1,8 @@
import { describe, test, expect, vi, beforeEach } from 'vitest';
import { screen } from '@testing-library/react';
import { renderWithProviders } from '../../../test/test-utils';
import NotificationPanel from '../NotificationPanel';
import { NotificationProvider } from '../../../contexts/NotificationContext';
import { renderWithProviders, setupTestEnvironment } from '../../../test/test-utils';
import React from 'react';
// Mock date-fns
@ -28,7 +28,6 @@ const createMockAnchorEl = () => {
describe('NotificationPanel - Simple Tests', () => {
beforeEach(() => {
setupTestEnvironment();
});
test('should not render when anchorEl is null', () => {
const { container } = renderWithProviders(

View File

@ -1,33 +1,30 @@
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
import { createComprehensiveAxiosMock, createComprehensiveApiMocks } from '../../../test/comprehensive-mocks';
// Mock axios comprehensively to prevent any real HTTP requests
vi.mock('axios', () => createComprehensiveAxiosMock());
// Mock API services comprehensively
vi.mock('../../../services/api', async () => {
const actual = await vi.importActual('../../../services/api');
const apiMocks = createComprehensiveApiMocks();
return {
...actual,
...apiMocks,
};
});
// Import after mocking
import { screen, fireEvent, act, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import UploadZone from '../UploadZone';
import { renderWithProviders, setupTestEnvironment, createMockApiServices } from '../../../test/test-utils';
import { renderWithProviders, createMockApiServices } from '../../../test/test-utils';
import { createMockLabel } from '../../../test/label-test-utils';
// Setup centralized API mocks for this component
const mockApiServices = createMockApiServices();
// Mock axios directly with our mock labels
vi.mock('axios', () => ({
default: {
create: vi.fn(() => ({
get: vi.fn().mockResolvedValue({
status: 200,
data: [createMockLabel({
name: 'Test Label',
color: '#0969da',
document_count: 0,
source_count: 0,
})]
}),
post: vi.fn().mockResolvedValue({ status: 201, data: {} }),
put: vi.fn().mockResolvedValue({ status: 200, data: {} }),
delete: vi.fn().mockResolvedValue({ status: 204 }),
})),
},
}));
const mockProps = {
onUploadComplete: vi.fn(),
};
@ -37,7 +34,6 @@ describe('UploadZone', () => {
beforeEach(() => {
vi.clearAllMocks();
setupTestEnvironment();
// Suppress console.error for "Failed to fetch labels" during tests
originalConsoleError = console.error;
console.error = vi.fn().mockImplementation((message, ...args) => {
@ -67,7 +63,7 @@ describe('UploadZone', () => {
});
test('shows accepted file types in UI', async () => {
await renderWithProvider(<UploadZone {...mockProps} />);
await renderWithProviders(<UploadZone {...mockProps} />);
// Wait for component to load
await waitFor(() => {
@ -79,7 +75,7 @@ describe('UploadZone', () => {
});
test('displays max file size limit', async () => {
await renderWithProvider(<UploadZone {...mockProps} />);
await renderWithProviders(<UploadZone {...mockProps} />);
await waitFor(() => {
expect(screen.getByText(/maximum file size/i)).toBeInTheDocument();
@ -89,7 +85,7 @@ describe('UploadZone', () => {
});
test('shows browse files button', async () => {
await renderWithProvider(<UploadZone {...mockProps} />);
await renderWithProviders(<UploadZone {...mockProps} />);
await waitFor(() => {
const browseButton = screen.getByRole('button', { name: /choose files/i });
@ -161,7 +157,7 @@ describe('UploadZone', () => {
test('handles click to browse files', async () => {
const user = userEvent.setup();
await renderWithProvider(<UploadZone {...mockProps} />);
await renderWithProviders(<UploadZone {...mockProps} />);
await waitFor(() => {
const browseButton = screen.getByRole('button', { name: /choose files/i });
@ -178,7 +174,7 @@ describe('UploadZone', () => {
});
test('renders upload zone structure correctly', async () => {
await renderWithProvider(<UploadZone {...mockProps} />);
await renderWithProviders(<UploadZone {...mockProps} />);
// Wait for component to load
await waitFor(() => {

View File

@ -1,16 +1,48 @@
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
import { screen, waitFor } from '@testing-library/react';
import FailedDocumentViewer from '../FailedDocumentViewer';
import { api } from '../../services/api';
import { renderWithProviders, setupTestEnvironment } from '../../test/test-utils';
// Mock the API
// Create mock function before imports
const mockApiGet = vi.fn();
// Mock the api module before importing anything else
vi.mock('../../services/api', () => ({
api: {
get: vi.fn(),
},
get: mockApiGet,
post: vi.fn(),
put: vi.fn(),
delete: vi.fn(),
patch: vi.fn(),
defaults: { headers: { common: {} } },
create: vi.fn(),
interceptors: {
request: { use: vi.fn(), eject: vi.fn() },
response: { use: vi.fn(), eject: vi.fn() }
}
}
}));
// Import after mocking
import { screen, waitFor } from '@testing-library/react';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import FailedDocumentViewer from '../FailedDocumentViewer';
import { renderWithProviders } from '../../test/test-utils';
const theme = createTheme();
// Mock URL constructor with static methods
const mockCreateObjectURL = vi.fn(() => 'mock-object-url');
const mockRevokeObjectURL = vi.fn();
global.URL = class URL {
constructor(url) {
this.href = url;
this.protocol = 'http:';
this.hostname = 'localhost';
this.pathname = '/';
this.search = '';
}
static createObjectURL = mockCreateObjectURL;
static revokeObjectURL = mockRevokeObjectURL;
} as any;
const defaultProps = {
failedDocumentId: 'test-failed-doc-id',
@ -26,23 +58,19 @@ const renderFailedDocumentViewer = (props = {}) => {
);
};
// Mock Blob and URL.createObjectURL
// Mock Blob
const mockBlob = vi.fn(() => ({
text: () => Promise.resolve('mock text content'),
}));
global.Blob = mockBlob as any;
const mockCreateObjectURL = vi.fn(() => 'mock-object-url');
const mockRevokeObjectURL = vi.fn();
global.URL = {
createObjectURL: mockCreateObjectURL,
revokeObjectURL: mockRevokeObjectURL,
} as any;
describe('FailedDocumentViewer', () => {
beforeEach(() => {
setupTestEnvironment();
vi.clearAllMocks();
// Set default mock response
mockApiGet.mockResolvedValue({
data: new Blob(['mock document content'], { type: 'application/pdf' })
});
});
afterEach(() => {
@ -52,7 +80,7 @@ describe('FailedDocumentViewer', () => {
describe('Loading State', () => {
test('should show loading spinner initially', () => {
// Mock API to never resolve
vi.mocked(api.get).mockImplementation(() => new Promise(() => {}));
mockApiGet.mockImplementation(() => new Promise(() => {}));
renderFailedDocumentViewer();
@ -60,7 +88,7 @@ describe('FailedDocumentViewer', () => {
});
test('should show loading spinner with correct styling', () => {
vi.mocked(api.get).mockImplementation(() => new Promise(() => {}));
mockApiGet.mockImplementation(() => new Promise(() => {}));
renderFailedDocumentViewer();
@ -79,12 +107,12 @@ describe('FailedDocumentViewer', () => {
const mockResponse = {
data: new Blob(['mock pdf content'], { type: 'application/pdf' }),
};
vi.mocked(api.get).mockResolvedValueOnce(mockResponse);
mockApiGet.mockResolvedValueOnce(mockResponse);
renderFailedDocumentViewer();
await waitFor(() => {
expect(api.get).toHaveBeenCalledWith('/documents/failed/test-failed-doc-id/view', {
expect(mockApiGet).toHaveBeenCalledWith('/documents/failed/test-failed-doc-id/view', {
responseType: 'blob'
});
});
@ -102,7 +130,7 @@ describe('FailedDocumentViewer', () => {
const mockResponse = {
data: new Blob(['mock image content'], { type: 'image/jpeg' }),
};
vi.mocked(api.get).mockResolvedValueOnce(mockResponse);
mockApiGet.mockResolvedValueOnce(mockResponse);
renderFailedDocumentViewer({
filename: 'test-image.jpg',
@ -125,7 +153,7 @@ describe('FailedDocumentViewer', () => {
const mockResponse = {
data: new Blob(['mock text content'], { type: 'text/plain' }),
};
vi.mocked(api.get).mockResolvedValueOnce(mockResponse);
mockApiGet.mockResolvedValueOnce(mockResponse);
renderFailedDocumentViewer({
filename: 'test-file.txt',
@ -143,7 +171,7 @@ describe('FailedDocumentViewer', () => {
const mockResponse = {
data: new Blob(['mock content'], { type: 'application/unknown' }),
};
vi.mocked(api.get).mockResolvedValueOnce(mockResponse);
mockApiGet.mockResolvedValueOnce(mockResponse);
renderFailedDocumentViewer({
filename: 'test-file.unknown',
@ -163,7 +191,7 @@ describe('FailedDocumentViewer', () => {
const error = {
response: { status: 404 }
};
vi.mocked(api.get).mockRejectedValueOnce(error);
mockApiGet.mockRejectedValueOnce(error);
renderFailedDocumentViewer();
@ -175,7 +203,7 @@ describe('FailedDocumentViewer', () => {
test('should show generic error for other failures', async () => {
const error = new Error('Network error');
vi.mocked(api.get).mockRejectedValueOnce(error);
mockApiGet.mockRejectedValueOnce(error);
renderFailedDocumentViewer();
@ -189,7 +217,7 @@ describe('FailedDocumentViewer', () => {
const error = {
response: { status: 500 }
};
vi.mocked(api.get).mockRejectedValueOnce(error);
mockApiGet.mockRejectedValueOnce(error);
renderFailedDocumentViewer();
@ -204,7 +232,7 @@ describe('FailedDocumentViewer', () => {
const mockResponse = {
data: new Blob(['mock content'], { type: 'application/pdf' }),
};
vi.mocked(api.get).mockResolvedValueOnce(mockResponse);
mockApiGet.mockResolvedValueOnce(mockResponse);
renderFailedDocumentViewer();
@ -222,34 +250,29 @@ describe('FailedDocumentViewer', () => {
const mockResponse = {
data: new Blob(['mock content'], { type: 'application/pdf' }),
};
vi.mocked(api.get).mockResolvedValue(mockResponse);
mockApiGet.mockResolvedValue(mockResponse);
const { rerender } = renderFailedDocumentViewer();
await waitFor(() => {
expect(api.get).toHaveBeenCalledWith('/documents/failed/test-failed-doc-id/view', {
expect(mockApiGet).toHaveBeenCalledWith('/documents/failed/test-failed-doc-id/view', {
responseType: 'blob'
});
});
// Change the failedDocumentId
const newProps = { ...defaultProps, failedDocumentId: "new-doc-id" };
rerender(
<ThemeProvider theme={theme}>
<FailedDocumentViewer
failedDocumentId="new-doc-id"
filename="test-document.pdf"
mimeType="application/pdf"
/>
</ThemeProvider>
<FailedDocumentViewer {...newProps} />
);
await waitFor(() => {
expect(api.get).toHaveBeenCalledWith('/documents/failed/new-doc-id/view', {
expect(mockApiGet).toHaveBeenCalledWith('/documents/failed/new-doc-id/view', {
responseType: 'blob'
});
});
expect(api.get).toHaveBeenCalledTimes(2);
expect(mockApiGet).toHaveBeenCalledTimes(2);
});
});
@ -258,7 +281,7 @@ describe('FailedDocumentViewer', () => {
const mockResponse = {
data: new Blob(['mock pdf content'], { type: 'application/pdf' }),
};
vi.mocked(api.get).mockResolvedValueOnce(mockResponse);
mockApiGet.mockResolvedValueOnce(mockResponse);
renderFailedDocumentViewer({
mimeType: 'application/pdf'
@ -278,7 +301,7 @@ describe('FailedDocumentViewer', () => {
const mockResponse = {
data: new Blob(['mock image content'], { type: mimeType }),
};
vi.mocked(api.get).mockResolvedValueOnce(mockResponse);
mockApiGet.mockResolvedValueOnce(mockResponse);
const filename = `test.${mimeType.split('/')[1]}`;
renderFailedDocumentViewer({
@ -304,7 +327,7 @@ describe('FailedDocumentViewer', () => {
const mockResponse = {
data: new Blob(['mock text content'], { type: mimeType }),
};
vi.mocked(api.get).mockResolvedValueOnce(mockResponse);
mockApiGet.mockResolvedValueOnce(mockResponse);
const filename = `test.${mimeType.split('/')[1]}`;
renderFailedDocumentViewer({
@ -329,7 +352,7 @@ describe('FailedDocumentViewer', () => {
const mockResponse = {
data: new Blob(['mock content'], { type: 'application/pdf' }),
};
vi.mocked(api.get).mockResolvedValueOnce(mockResponse);
mockApiGet.mockResolvedValueOnce(mockResponse);
renderFailedDocumentViewer();
@ -343,7 +366,7 @@ describe('FailedDocumentViewer', () => {
const mockResponse = {
data: new Blob(['mock image content'], { type: 'image/jpeg' }),
};
vi.mocked(api.get).mockResolvedValueOnce(mockResponse);
mockApiGet.mockResolvedValueOnce(mockResponse);
renderFailedDocumentViewer({
mimeType: 'image/jpeg'
@ -363,12 +386,12 @@ describe('FailedDocumentViewer', () => {
const mockResponse = {
data: new Blob(['mock content'], { type: 'application/pdf' }),
};
vi.mocked(api.get).mockResolvedValueOnce(mockResponse);
mockApiGet.mockResolvedValueOnce(mockResponse);
renderFailedDocumentViewer();
await waitFor(() => {
expect(api.get).toHaveBeenCalledWith('/documents/failed/test-failed-doc-id/view', {
expect(mockApiGet).toHaveBeenCalledWith('/documents/failed/test-failed-doc-id/view', {
responseType: 'blob'
});
});
@ -378,14 +401,14 @@ describe('FailedDocumentViewer', () => {
const mockResponse = {
data: new Blob(['mock content'], { type: 'application/pdf' }),
};
vi.mocked(api.get).mockResolvedValueOnce(mockResponse);
mockApiGet.mockResolvedValueOnce(mockResponse);
renderFailedDocumentViewer({
failedDocumentId: 'different-doc-id'
});
await waitFor(() => {
expect(api.get).toHaveBeenCalledWith('/documents/failed/different-doc-id/view', {
expect(mockApiGet).toHaveBeenCalledWith('/documents/failed/different-doc-id/view', {
responseType: 'blob'
});
});
@ -397,7 +420,7 @@ describe('FailedDocumentViewer', () => {
const mockResponse = {
data: new Blob([], { type: 'application/pdf' }),
};
vi.mocked(api.get).mockResolvedValueOnce(mockResponse);
mockApiGet.mockResolvedValueOnce(mockResponse);
renderFailedDocumentViewer();
@ -413,7 +436,7 @@ describe('FailedDocumentViewer', () => {
const mockResponse = {
data: new Blob(['mock content'], { type: 'application/pdf' }),
};
vi.mocked(api.get).mockResolvedValueOnce(mockResponse);
mockApiGet.mockResolvedValueOnce(mockResponse);
renderFailedDocumentViewer({
filename: longFilename
@ -429,7 +452,7 @@ describe('FailedDocumentViewer', () => {
const mockResponse = {
data: new Blob(['mock content'], { type: 'application/pdf' }),
};
vi.mocked(api.get).mockResolvedValueOnce(mockResponse);
mockApiGet.mockResolvedValueOnce(mockResponse);
renderFailedDocumentViewer({
filename: specialFilename
@ -444,7 +467,7 @@ describe('FailedDocumentViewer', () => {
const mockResponse = {
data: new Blob(['mock content'], { type: '' }),
};
vi.mocked(api.get).mockResolvedValueOnce(mockResponse);
mockApiGet.mockResolvedValueOnce(mockResponse);
renderFailedDocumentViewer({
mimeType: undefined as any
@ -462,7 +485,7 @@ describe('FailedDocumentViewer', () => {
const mockResponse = {
data: new Blob(['mock content'], { type: 'application/pdf' }),
};
vi.mocked(api.get).mockResolvedValueOnce(mockResponse);
mockApiGet.mockResolvedValueOnce(mockResponse);
renderFailedDocumentViewer();
@ -476,7 +499,7 @@ describe('FailedDocumentViewer', () => {
const mockResponse = {
data: new Blob(['mock image content'], { type: 'image/jpeg' }),
};
vi.mocked(api.get).mockResolvedValueOnce(mockResponse);
mockApiGet.mockResolvedValueOnce(mockResponse);
renderFailedDocumentViewer({
mimeType: 'image/jpeg'

View File

@ -1,19 +1,33 @@
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { RetryRecommendations } from '../RetryRecommendations';
import { createComprehensiveAxiosMock, createComprehensiveApiMocks } from '../../test/comprehensive-mocks';
// Create unique mock functions for this test file
// Mock axios comprehensively to prevent any real HTTP requests
vi.mock('axios', () => createComprehensiveAxiosMock());
// Create mock functions for this specific test
const mockGetRetryRecommendations = vi.fn();
const mockBulkRetryOcr = vi.fn();
// Mock the API module with a unique namespace for this test
vi.mock('../../services/api', () => ({
documentService: {
getRetryRecommendations: mockGetRetryRecommendations,
bulkRetryOcr: mockBulkRetryOcr,
},
}));
// Mock the API module with comprehensive mocking
vi.mock('../../services/api', async () => {
const actual = await vi.importActual('../../services/api');
const apiMocks = createComprehensiveApiMocks();
return {
...actual,
...apiMocks,
documentService: {
...apiMocks.documentService,
getRetryRecommendations: mockGetRetryRecommendations,
bulkRetryOcr: mockBulkRetryOcr,
},
};
});
// Import after mocking
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { RetryRecommendations } from '../RetryRecommendations';
describe('RetryRecommendations', () => {
const mockProps = {

View File

@ -1,8 +1,8 @@
import { describe, test, expect, vi, beforeEach } from 'vitest';
import { screen, fireEvent } from '@testing-library/react';
import { renderWithProviders } from '../../test/test-utils';
import TestNotification from '../TestNotification';
import { NotificationProvider } from '../../contexts/NotificationContext';
import { renderWithProviders, setupTestEnvironment } from '../../test/test-utils';
import React from 'react';
const renderTestNotification = () => {
@ -15,7 +15,6 @@ const renderTestNotification = () => {
describe('TestNotification', () => {
beforeEach(() => {
setupTestEnvironment();
vi.clearAllMocks();
});

View File

@ -1,7 +1,7 @@
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
import { screen, act } from '@testing-library/react';
import { screen, act, render } from '@testing-library/react';
import { NotificationProvider, useNotifications } from '../NotificationContext';
import { renderWithProviders, setupTestEnvironment } from '../../test/test-utils';
import { renderWithProviders } from '../../test/test-utils';
import React from 'react';
// Simple test component
@ -62,7 +62,6 @@ const renderWithProvider = () => {
describe('NotificationContext - Simple Tests', () => {
beforeEach(() => {
setupTestEnvironment();
vi.useFakeTimers();
});
@ -164,7 +163,7 @@ describe('NotificationContext - Simple Tests', () => {
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
expect(() => {
renderWithProviders(<SimpleTestComponent />);
render(<SimpleTestComponent />);
}).toThrow('useNotifications must be used within NotificationProvider');
consoleSpy.mockRestore();
@ -198,7 +197,6 @@ describe('NotificationContext - Types', () => {
};
beforeEach(() => {
setupTestEnvironment();
vi.useFakeTimers();
});

View File

@ -1,40 +1,31 @@
import { describe, test, expect, vi, beforeEach } from 'vitest';
import { createComprehensiveAxiosMock, createComprehensiveApiMocks } from '../../test/comprehensive-mocks';
// Mock axios comprehensively to prevent any real HTTP requests
vi.mock('axios', () => createComprehensiveAxiosMock());
// Mock API services comprehensively
vi.mock('../../services/api', async () => {
const actual = await vi.importActual('../../services/api');
const apiMocks = createComprehensiveApiMocks();
return {
...actual,
...apiMocks,
};
});
// Import components AFTER the mocks are set up
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import DocumentManagementPage from '../DocumentManagementPage';
import userEvent from '@testing-library/user-event';
import DocumentManagementPage from '../DocumentManagementPage';
// Create mock objects first
const mockDocumentService = {
getFailedDocuments: vi.fn(),
getFailedOcrDocuments: vi.fn(),
getDuplicates: vi.fn(),
retryOcr: vi.fn(),
deleteLowConfidence: vi.fn(),
deleteFailedOcr: vi.fn(),
downloadFile: vi.fn(),
getRetryRecommendations: vi.fn(),
getRetryStats: vi.fn(),
getDocumentRetryHistory: vi.fn(),
};
const mockQueueService = {
requeueFailed: vi.fn(),
};
const mockApi = {
get: vi.fn(),
delete: vi.fn(),
bulkRetryOcr: vi.fn(),
};
// Mock API with comprehensive responses
vi.mock('../../services/api', () => ({
api: mockApi,
documentService: mockDocumentService,
queueService: mockQueueService,
}));
// Get references to the mocked modules using dynamic import
const { api, documentService } = await import('../../services/api');
const mockApi = api;
const mockDocumentService = documentService;
const theme = createTheme();
@ -51,40 +42,11 @@ const DocumentManagementPageWrapper = ({ children }: { children: React.ReactNode
describe('DocumentManagementPage - Runtime Error Prevention', () => {
beforeEach(() => {
vi.clearAllMocks();
mockDocumentService.getFailedDocuments.mockClear();
mockDocumentService.getFailedOcrDocuments.mockClear();
mockDocumentService.getDuplicates.mockClear();
mockQueueService.requeueFailed.mockClear();
// Setup default mock returns for retry functionality
mockDocumentService.getRetryRecommendations.mockResolvedValue({
data: { recommendations: [], total_recommendations: 0 }
});
mockDocumentService.getRetryStats.mockResolvedValue({
data: { failure_reasons: [], file_types: [], total_failed: 0 }
});
mockDocumentService.getDocumentRetryHistory.mockResolvedValue({
data: { document_id: 'test', retry_history: [], total_retries: 0 }
});
mockApi.bulkRetryOcr.mockResolvedValue({
data: { success: true, queued_count: 0, matched_count: 0, documents: [] }
});
});
describe('OCR Confidence Display - Null Safety', () => {
test('basic rendering test', async () => {
// Very basic test first - wait for loading to complete
mockDocumentService.getFailedDocuments.mockResolvedValue({
data: {
documents: [],
pagination: { total: 0, limit: 25, offset: 0, total_pages: 0 },
statistics: { total_failed: 0, by_reason: {}, by_stage: {} },
},
});
mockApi.get.mockResolvedValue({
data: { total_count: 0, total_size: 0 },
});
// With axios mocked directly, all API calls should return empty data by default
render(
<DocumentManagementPageWrapper>
@ -559,9 +521,8 @@ describe('DocumentManagementPage - Runtime Error Prevention', () => {
describe('Ignored Files Tab Functionality', () => {
test('should render ignored files tab without errors', async () => {
// Mock ignored files API responses
const { api } = await import('../../services/api');
vi.mocked(api.get).mockImplementation((url) => {
// Setup mock responses using our already defined mocks
mockApi.get.mockImplementation((url) => {
if (url.includes('/ignored-files/stats')) {
return Promise.resolve({
data: {
@ -582,8 +543,7 @@ describe('DocumentManagementPage - Runtime Error Prevention', () => {
return Promise.resolve({ data: {} });
});
const { documentService } = await import('../../services/api');
vi.mocked(documentService.getFailedDocuments).mockResolvedValueOnce({
mockDocumentService.getFailedDocuments.mockResolvedValueOnce({
data: {
documents: [],
pagination: { total: 0, limit: 25, offset: 0, total_pages: 1 },

View File

@ -3,7 +3,8 @@ import { screen, waitFor, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import LabelsPage from '../LabelsPage';
import * as useApiModule from '../../hooks/useApi';
import { renderWithAuthenticatedUser, setupTestEnvironment } from '../../test/test-utils';\nimport { createMockLabel } from '../../test/label-test-utils';
import { renderWithAuthenticatedUser } from '../../test/test-utils';
import { createMockLabel } from '../../test/label-test-utils';
const mockLabels = [
createMockLabel({
@ -66,7 +67,6 @@ describe('LabelsPage Component', () => {
};
beforeEach(() => {
setupTestEnvironment();
user = userEvent.setup();
mockApi = {

View File

@ -1,23 +1,26 @@
import { describe, test, expect, vi, beforeEach } from 'vitest';
import { screen } from '@testing-library/react';
import { renderWithAuthenticatedUser } from '../../test/test-utils';
import { createComprehensiveAxiosMock, createComprehensiveApiMocks } from '../../test/comprehensive-mocks';
import SearchPage from '../SearchPage';
import { renderWithAuthenticatedUser, createMockUser, setupTestEnvironment } from '../../test/test-utils';
// Mock API functions
vi.mock('../../services/api', () => ({
searchDocuments: vi.fn(() => Promise.resolve({
results: [],
total: 0,
page: 1,
page_size: 20
})),
getSettings: vi.fn(() => Promise.resolve({})),
}));
// Mock axios comprehensively to prevent any real HTTP requests
vi.mock('axios', () => createComprehensiveAxiosMock());
// Mock API services comprehensively
vi.mock('../../services/api', async () => {
const actual = await vi.importActual('../../services/api');
const apiMocks = createComprehensiveApiMocks();
return {
...actual,
...apiMocks,
};
});
describe('SearchPage', () => {
beforeEach(() => {
vi.clearAllMocks();
setupTestEnvironment();
});
test('renders search page structure', () => {

View File

@ -2,11 +2,26 @@ import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import api from '../../services/api';
import { createComprehensiveAxiosMock, createComprehensiveApiMocks } from '../../test/comprehensive-mocks';
// Mock the API
vi.mock('../../services/api');
const mockedApi = vi.mocked(api);
// Mock axios comprehensively to prevent any real HTTP requests
vi.mock('axios', () => createComprehensiveAxiosMock());
// Mock API services comprehensively
vi.mock('../../services/api', async () => {
const actual = await vi.importActual('../../services/api');
const apiMocks = createComprehensiveApiMocks();
return {
...actual,
default: apiMocks.api, // Since this file imports `api` as default
...apiMocks,
};
});
// Get references to the mocked modules using dynamic import
const { default: api } = await import('../../services/api');
const mockedApi = api;
// Mock settings with WebDAV configuration
const mockSettings = {

View File

@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { type OcrResponse, type Document } from '../api';
import { createMockApiServices, setupTestEnvironment } from '../../test/test-utils';
import { createMockApiServices } from '../../test/test-utils';
// Use centralized API mocking
const mockServices = createMockApiServices();
@ -21,7 +21,6 @@ const { documentService } = await import('../api');
describe('documentService', () => {
beforeEach(() => {
vi.clearAllMocks();
setupTestEnvironment();
});
describe('getOcrText', () => {
@ -124,21 +123,21 @@ describe('documentService', () => {
});
it('should make correct API call', async () => {
mockGetOcrText.mockResolvedValue({ data: mockOcrResponse });
mockDocumentService.getOcrText.mockResolvedValue({ data: mockOcrResponse });
await documentService.getOcrText('doc-123');
expect(mockGetOcrText).toHaveBeenCalledWith('doc-123');
expect(mockDocumentService.getOcrText).toHaveBeenCalledWith('doc-123');
});
it('should handle network errors', async () => {
mockGetOcrText.mockRejectedValue(new Error('Network Error'));
mockDocumentService.getOcrText.mockRejectedValue(new Error('Network Error'));
await expect(documentService.getOcrText('doc-123')).rejects.toThrow('Network Error');
});
it('should handle 404 errors for non-existent documents', async () => {
mockGetOcrText.mockRejectedValue({
mockDocumentService.getOcrText.mockRejectedValue({
response: {
status: 404,
data: { error: 'Document not found' },
@ -153,7 +152,7 @@ describe('documentService', () => {
});
it('should handle 401 unauthorized errors', async () => {
mockGetOcrText.mockRejectedValue({
mockDocumentService.getOcrText.mockRejectedValue({
response: {
status: 401,
data: { error: 'Unauthorized' },
@ -209,7 +208,7 @@ describe('documentService', () => {
config: {},
};
mockList.mockResolvedValue(mockResponse);
mockDocumentService.list.mockResolvedValue(mockResponse);
const result = await documentService.list(50, 0);
@ -236,24 +235,24 @@ describe('documentService', () => {
ocr_status: 'pending',
};
mockUpload.mockResolvedValue({ data: mockUploadResponse });
mockDocumentService.upload.mockResolvedValue({ data: mockUploadResponse });
const result = await documentService.upload(mockFile);
expect(result.data).toEqual(mockUploadResponse);
expect(mockUpload).toHaveBeenCalledWith(mockFile);
expect(mockDocumentService.upload).toHaveBeenCalledWith(mockFile);
});
});
describe('download', () => {
it('should download file as blob', async () => {
const mockBlob = new Blob(['file content'], { type: 'application/pdf' });
mockDownload.mockResolvedValue({ data: mockBlob });
mockDocumentService.download.mockResolvedValue({ data: mockBlob });
const result = await documentService.download('doc-123');
expect(result.data).toEqual(mockBlob);
expect(mockDownload).toHaveBeenCalledWith('doc-123');
expect(mockDocumentService.download).toHaveBeenCalledWith('doc-123');
});
});
});
@ -325,11 +324,11 @@ describe('documentService.deleteLowConfidence', () => {
config: {},
};
mockDeleteLowConfidence.mockResolvedValue(mockDeleteResponse);
mockDocumentService.deleteLowConfidence.mockResolvedValue(mockDeleteResponse);
const result = await documentService.deleteLowConfidence(30.0, false);
expect(mockDeleteLowConfidence).toHaveBeenCalledWith(30.0, false);
expect(mockDocumentService.deleteLowConfidence).toHaveBeenCalledWith(30.0, false);
expect(result.data.success).toBe(true);
expect(result.data.deleted_count).toBe(3);
expect(result.data.matched_count).toBe(3);
@ -351,11 +350,11 @@ describe('documentService.deleteLowConfidence', () => {
config: {},
};
mockDeleteLowConfidence.mockResolvedValue(mockPreviewResponse);
mockDocumentService.deleteLowConfidence.mockResolvedValue(mockPreviewResponse);
const result = await documentService.deleteLowConfidence(50.0, true);
expect(mockDeleteLowConfidence).toHaveBeenCalledWith(50.0, true);
expect(mockDocumentService.deleteLowConfidence).toHaveBeenCalledWith(50.0, true);
expect(result.data.success).toBe(true);
expect(result.data.preview).toBe(true);
expect(result.data.matched_count).toBe(5);
@ -376,11 +375,11 @@ describe('documentService.deleteLowConfidence', () => {
config: {},
};
mockDeleteLowConfidence.mockResolvedValue(mockEmptyResponse);
mockDocumentService.deleteLowConfidence.mockResolvedValue(mockEmptyResponse);
const result = await documentService.deleteLowConfidence(10.0, false);
expect(mockDeleteLowConfidence).toHaveBeenCalledWith(10.0, false);
expect(mockDocumentService.deleteLowConfidence).toHaveBeenCalledWith(10.0, false);
expect(result.data.success).toBe(true);
expect(result.data.deleted_count).toBe(0);
});
@ -398,23 +397,23 @@ describe('documentService.deleteLowConfidence', () => {
config: {},
};
mockDeleteLowConfidence.mockResolvedValue(mockErrorResponse);
mockDocumentService.deleteLowConfidence.mockResolvedValue(mockErrorResponse);
const result = await documentService.deleteLowConfidence(-10.0, false);
expect(mockDeleteLowConfidence).toHaveBeenCalledWith(-10.0, false);
expect(mockDocumentService.deleteLowConfidence).toHaveBeenCalledWith(-10.0, false);
expect(result.data.success).toBe(false);
expect(result.data.message).toContain('must be between 0.0 and 100.0');
});
it('should handle API errors gracefully', async () => {
const mockError = new Error('Network error');
mockDeleteLowConfidence.mockRejectedValue(mockError);
mockDocumentService.deleteLowConfidence.mockRejectedValue(mockError);
await expect(documentService.deleteLowConfidence(30.0, false))
.rejects.toThrow('Network error');
expect(mockDeleteLowConfidence).toHaveBeenCalledWith(30.0, false);
expect(mockDocumentService.deleteLowConfidence).toHaveBeenCalledWith(30.0, false);
});
it('should use correct default values', async () => {
@ -426,12 +425,12 @@ describe('documentService.deleteLowConfidence', () => {
config: {},
};
mockDeleteLowConfidence.mockResolvedValue(mockResponse);
mockDocumentService.deleteLowConfidence.mockResolvedValue(mockResponse);
// Test with explicit false value (the default)
await documentService.deleteLowConfidence(40.0, false);
expect(mockDeleteLowConfidence).toHaveBeenCalledWith(40.0, false);
expect(mockDocumentService.deleteLowConfidence).toHaveBeenCalledWith(40.0, false);
});
it('should handle partial deletion failures', async () => {
@ -452,7 +451,7 @@ describe('documentService.deleteLowConfidence', () => {
config: {},
};
mockDeleteLowConfidence.mockResolvedValue(mockPartialFailureResponse);
mockDocumentService.deleteLowConfidence.mockResolvedValue(mockPartialFailureResponse);
const result = await documentService.deleteLowConfidence(25.0, false);
@ -472,15 +471,15 @@ describe('documentService.deleteLowConfidence', () => {
config: {},
};
mockDeleteLowConfidence.mockResolvedValue(mockResponse);
mockDocumentService.deleteLowConfidence.mockResolvedValue(mockResponse);
// Test various confidence values
const testValues = [0.0, 0.1, 30.5, 50.0, 99.9, 100.0];
for (const confidence of testValues) {
mockDeleteLowConfidence.mockClear();
mockDocumentService.deleteLowConfidence.mockClear();
await documentService.deleteLowConfidence(confidence, true);
expect(mockDeleteLowConfidence).toHaveBeenCalledWith(confidence, true);
expect(mockDocumentService.deleteLowConfidence).toHaveBeenCalledWith(confidence, true);
}
});
});
@ -529,11 +528,11 @@ describe('documentService.getFailedOcrDocuments', () => {
config: {},
};
mockGetFailedOcrDocuments.mockResolvedValue(mockResponse);
mockDocumentService.getFailedOcrDocuments.mockResolvedValue(mockResponse);
const result = await documentService.getFailedOcrDocuments(50, 0);
expect(mockGetFailedOcrDocuments).toHaveBeenCalledWith(50, 0);
expect(mockDocumentService.getFailedOcrDocuments).toHaveBeenCalledWith(50, 0);
expect(result.data).toEqual(mockFailedOcrResponse);
expect(result.data.documents).toHaveLength(2);
expect(result.data.documents[0].failure_stage).toBe('ocr');
@ -541,19 +540,19 @@ describe('documentService.getFailedOcrDocuments', () => {
});
it('should handle pagination parameters correctly', async () => {
mockGetFailedOcrDocuments.mockResolvedValue({ data: mockFailedOcrResponse });
mockDocumentService.getFailedOcrDocuments.mockResolvedValue({ data: mockFailedOcrResponse });
await documentService.getFailedOcrDocuments(25, 10);
expect(mockGetFailedOcrDocuments).toHaveBeenCalledWith(25, 10);
expect(mockDocumentService.getFailedOcrDocuments).toHaveBeenCalledWith(25, 10);
});
it('should use default pagination when not specified', async () => {
mockGetFailedOcrDocuments.mockResolvedValue({ data: mockFailedOcrResponse });
mockDocumentService.getFailedOcrDocuments.mockResolvedValue({ data: mockFailedOcrResponse });
await documentService.getFailedOcrDocuments();
expect(mockGetFailedOcrDocuments).toHaveBeenCalledWith();
expect(mockDocumentService.getFailedOcrDocuments).toHaveBeenCalledWith();
});
it('should handle empty results', async () => {
@ -563,7 +562,7 @@ describe('documentService.getFailedOcrDocuments', () => {
statistics: { total_failed: 0, failure_categories: [] }
};
mockGetFailedOcrDocuments.mockResolvedValue({ data: emptyResponse });
mockDocumentService.getFailedOcrDocuments.mockResolvedValue({ data: emptyResponse });
const result = await documentService.getFailedOcrDocuments();
@ -574,7 +573,7 @@ describe('documentService.getFailedOcrDocuments', () => {
it('should handle API errors', async () => {
const mockError = new Error('Network error');
mockGetFailedOcrDocuments.mockRejectedValue(mockError);
mockDocumentService.getFailedOcrDocuments.mockRejectedValue(mockError);
await expect(documentService.getFailedOcrDocuments()).rejects.toThrow('Network error');
});
@ -625,11 +624,11 @@ describe('documentService.getFailedDocuments', () => {
};
it('should fetch failed documents with default parameters', async () => {
mockGetFailedDocuments.mockResolvedValue({ data: mockFailedDocumentsResponse });
mockDocumentService.getFailedDocuments.mockResolvedValue({ data: mockFailedDocumentsResponse });
const result = await documentService.getFailedDocuments();
expect(mockGetFailedDocuments).toHaveBeenCalledWith();
expect(mockDocumentService.getFailedDocuments).toHaveBeenCalledWith();
expect(result.data).toEqual(mockFailedDocumentsResponse);
expect(result.data.documents).toHaveLength(3);
});
@ -642,11 +641,11 @@ describe('documentService.getFailedDocuments', () => {
statistics: { total_failed: 1, failure_categories: [{ reason: 'low_ocr_confidence', display_name: 'Low OCR Confidence', count: 1 }] }
};
mockGetFailedDocuments.mockResolvedValue({ data: ocrOnlyResponse });
mockDocumentService.getFailedDocuments.mockResolvedValue({ data: ocrOnlyResponse });
const result = await documentService.getFailedDocuments(25, 0, 'ocr');
expect(mockGetFailedDocuments).toHaveBeenCalledWith(25, 0, 'ocr');
expect(mockDocumentService.getFailedDocuments).toHaveBeenCalledWith(25, 0, 'ocr');
expect(result.data.documents).toHaveLength(1);
expect(result.data.documents[0].failure_stage).toBe('ocr');
});
@ -659,11 +658,11 @@ describe('documentService.getFailedDocuments', () => {
statistics: { total_failed: 1, failure_categories: [{ reason: 'duplicate_content', display_name: 'Duplicate Content', count: 1 }] }
};
mockGetFailedDocuments.mockResolvedValue({ data: duplicateOnlyResponse });
mockDocumentService.getFailedDocuments.mockResolvedValue({ data: duplicateOnlyResponse });
const result = await documentService.getFailedDocuments(25, 0, undefined, 'duplicate_content');
expect(mockGetFailedDocuments).toHaveBeenCalledWith(25, 0, undefined, 'duplicate_content');
expect(mockDocumentService.getFailedDocuments).toHaveBeenCalledWith(25, 0, undefined, 'duplicate_content');
expect(result.data.documents).toHaveLength(1);
expect(result.data.documents[0].failure_reason).toBe('duplicate_content');
});
@ -676,22 +675,22 @@ describe('documentService.getFailedDocuments', () => {
statistics: { total_failed: 1, failure_categories: [{ reason: 'low_ocr_confidence', display_name: 'Low OCR Confidence', count: 1 }] }
};
mockGetFailedDocuments.mockResolvedValue({ data: filteredResponse });
mockDocumentService.getFailedDocuments.mockResolvedValue({ data: filteredResponse });
const result = await documentService.getFailedDocuments(25, 0, 'ocr', 'low_ocr_confidence');
expect(mockGetFailedDocuments).toHaveBeenCalledWith(25, 0, 'ocr', 'low_ocr_confidence');
expect(mockDocumentService.getFailedDocuments).toHaveBeenCalledWith(25, 0, 'ocr', 'low_ocr_confidence');
expect(result.data.documents).toHaveLength(1);
expect(result.data.documents[0].failure_stage).toBe('ocr');
expect(result.data.documents[0].failure_reason).toBe('low_ocr_confidence');
});
it('should handle custom pagination', async () => {
mockGetFailedDocuments.mockResolvedValue({ data: mockFailedDocumentsResponse });
mockDocumentService.getFailedDocuments.mockResolvedValue({ data: mockFailedDocumentsResponse });
await documentService.getFailedDocuments(10, 20);
expect(mockGetFailedDocuments).toHaveBeenCalledWith(10, 20);
expect(mockDocumentService.getFailedDocuments).toHaveBeenCalledWith(10, 20);
});
it('should handle empty results', async () => {
@ -701,7 +700,7 @@ describe('documentService.getFailedDocuments', () => {
statistics: { total_failed: 0, failure_categories: [] }
};
mockGetFailedDocuments.mockResolvedValue({ data: emptyResponse });
mockDocumentService.getFailedDocuments.mockResolvedValue({ data: emptyResponse });
const result = await documentService.getFailedDocuments();
@ -724,11 +723,11 @@ describe('documentService.retryOcr', () => {
config: {},
};
mockRetryOcr.mockResolvedValue(mockRetryResponse);
mockDocumentService.retryOcr.mockResolvedValue(mockRetryResponse);
const result = await documentService.retryOcr('doc-123');
expect(mockRetryOcr).toHaveBeenCalledWith('doc-123');
expect(mockDocumentService.retryOcr).toHaveBeenCalledWith('doc-123');
expect(result.data.success).toBe(true);
expect(result.data.document_id).toBe('doc-123');
});
@ -741,7 +740,7 @@ describe('documentService.retryOcr', () => {
}
};
mockRetryOcr.mockRejectedValue(mockError);
mockDocumentService.retryOcr.mockRejectedValue(mockError);
await expect(documentService.retryOcr('non-existent-doc')).rejects.toMatchObject({
response: { status: 404 }
@ -749,7 +748,7 @@ describe('documentService.retryOcr', () => {
});
it('should handle network errors', async () => {
mockRetryOcr.mockRejectedValue(new Error('Network error'));
mockDocumentService.retryOcr.mockRejectedValue(new Error('Network error'));
await expect(documentService.retryOcr('doc-123')).rejects.toThrow('Network error');
});

View File

@ -0,0 +1,129 @@
// Comprehensive mocking utilities to prevent HTTP requests in tests
import { vi } from 'vitest';
/**
* Creates a comprehensive axios mock that prevents all HTTP requests
* This should be used in test files that have components making API calls
*/
export const createComprehensiveAxiosMock = () => {
const mockAxiosInstance = {
get: vi.fn().mockResolvedValue({ data: {} }),
post: vi.fn().mockResolvedValue({ data: { success: true } }),
put: vi.fn().mockResolvedValue({ data: { success: true } }),
delete: vi.fn().mockResolvedValue({ data: { success: true } }),
patch: vi.fn().mockResolvedValue({ data: { success: true } }),
request: vi.fn().mockResolvedValue({ data: { success: true } }),
head: vi.fn().mockResolvedValue({ data: {} }),
options: vi.fn().mockResolvedValue({ data: {} }),
defaults: {
headers: {
common: {},
get: {},
post: {},
put: {},
delete: {},
patch: {},
}
},
interceptors: {
request: { use: vi.fn(), eject: vi.fn() },
response: { use: vi.fn(), eject: vi.fn() },
},
};
return {
default: {
create: vi.fn(() => mockAxiosInstance),
...mockAxiosInstance,
},
};
};
/**
* Creates comprehensive API service mocks
*/
export const createComprehensiveApiMocks = () => ({
api: {
get: vi.fn().mockResolvedValue({ data: {} }),
post: vi.fn().mockResolvedValue({ data: { success: true } }),
put: vi.fn().mockResolvedValue({ data: { success: true } }),
delete: vi.fn().mockResolvedValue({ data: { success: true } }),
patch: vi.fn().mockResolvedValue({ data: { success: true } }),
defaults: { headers: { common: {} } },
},
documentService: {
getRetryRecommendations: vi.fn().mockResolvedValue({
data: { recommendations: [], total_recommendations: 0 }
}),
bulkRetryOcr: vi.fn().mockResolvedValue({ data: { success: true } }),
getFailedDocuments: vi.fn().mockResolvedValue({
data: {
documents: [],
pagination: { total: 0, limit: 25, offset: 0, total_pages: 1 },
statistics: { total_failed: 0, by_reason: {}, by_stage: {} },
},
}),
getDuplicates: vi.fn().mockResolvedValue({
data: {
duplicates: [],
pagination: { total: 0, limit: 25, offset: 0, has_more: false },
statistics: { total_duplicate_groups: 0 },
},
}),
enhancedSearch: vi.fn().mockResolvedValue({
data: {
documents: [],
total: 0,
query_time_ms: 0,
suggestions: []
}
}),
search: vi.fn().mockResolvedValue({
data: {
documents: [],
total: 0,
query_time_ms: 0,
suggestions: []
}
}),
getOcrText: vi.fn().mockResolvedValue({ data: {} }),
upload: vi.fn().mockResolvedValue({ data: {} }),
list: vi.fn().mockResolvedValue({ data: [] }),
listWithPagination: vi.fn().mockResolvedValue({
data: {
documents: [],
pagination: { total: 0, limit: 20, offset: 0, has_more: false }
}
}),
delete: vi.fn().mockResolvedValue({}),
bulkDelete: vi.fn().mockResolvedValue({}),
retryOcr: vi.fn().mockResolvedValue({}),
getFacets: vi.fn().mockResolvedValue({ data: { mime_types: [], tags: [] } }),
download: vi.fn().mockResolvedValue({ data: new Blob() }),
deleteLowConfidence: vi.fn().mockResolvedValue({ data: {} }),
deleteFailedOcr: vi.fn().mockResolvedValue({ data: {} }),
view: vi.fn().mockResolvedValue({ data: new Blob() }),
getThumbnail: vi.fn().mockResolvedValue({ data: new Blob() }),
getProcessedImage: vi.fn().mockResolvedValue({ data: new Blob() }),
downloadFile: vi.fn().mockResolvedValue(undefined),
getRetryStats: vi.fn().mockResolvedValue({ data: {} }),
getDocumentRetryHistory: vi.fn().mockResolvedValue({ data: [] }),
},
queueService: {
getQueueStatus: vi.fn().mockResolvedValue({ data: { active: 0, waiting: 0 } }),
},
});
/**
* Standard pattern for mocking both axios and API services
* Use this at the top of test files that have components making API calls
*/
export const setupHttpMocks = () => {
// Mock axios comprehensively
vi.mock('axios', () => createComprehensiveAxiosMock());
// Mock API services
const apiMocks = createComprehensiveApiMocks();
return apiMocks;
};

View File

@ -5,8 +5,11 @@ import { type LabelData } from '../components/Labels/Label';
* Test utilities for label-related tests
*/
// Counter for generating unique IDs
let labelIdCounter = 1;
export const createMockLabel = (overrides: Partial<LabelData> = {}): LabelData => ({
id: 'test-label-1',
id: `test-label-${labelIdCounter++}`,
name: 'Test Label',
description: 'A test label',
color: '#0969da',
@ -21,12 +24,15 @@ export const createMockLabel = (overrides: Partial<LabelData> = {}): LabelData =
});
export const createMockSystemLabel = (overrides: Partial<LabelData> = {}): LabelData => ({
...createMockLabel(),
id: 'system-label-1',
id: `system-label-${labelIdCounter++}`,
name: 'Important',
description: 'A test label',
color: '#d73a49',
background_color: undefined,
icon: 'star',
is_system: true,
created_at: '2024-01-01T00:00:00Z',
updated_at: '2024-01-01T00:00:00Z',
document_count: 10,
source_count: 2,
...overrides,
@ -213,12 +219,12 @@ export const testDataBuilders = {
* Creates a set of labels that represent typical usage patterns
*/
createTypicalLabelSet: (): LabelData[] => [
createMockSystemLabel({ name: 'Important', color: '#d73a49', icon: 'star' }),
createMockSystemLabel({ name: 'Work', color: '#0969da', icon: 'work' }),
createMockSystemLabel({ name: 'Personal', color: '#28a745', icon: 'person' }),
createMockLabel({ name: 'Project Alpha', color: '#8250df', icon: 'folder' }),
createMockLabel({ name: 'Invoices', color: '#fb8500', icon: 'receipt' }),
createMockLabel({ name: 'Archive', color: '#6e7781', icon: 'archive', document_count: 0 }),
createMockSystemLabel({ name: 'Important', description: 'High priority items', color: '#d73a49', icon: 'star' }),
createMockSystemLabel({ name: 'Work', description: 'Work-related documents', color: '#0969da', icon: 'work' }),
createMockSystemLabel({ name: 'Personal', description: 'Personal documents', color: '#28a745', icon: 'person' }),
createMockLabel({ name: 'Project Alpha', description: 'My personal project files', color: '#8250df', icon: 'folder' }),
createMockLabel({ name: 'Invoices', description: 'Financial documents', color: '#fb8500', icon: 'receipt' }),
createMockLabel({ name: 'Archive', description: 'Archived items', color: '#6e7781', icon: 'archive', document_count: 0 }),
],
/**

View File

@ -3,7 +3,7 @@
import '@testing-library/jest-dom'
import { vi } from 'vitest'
import { setupTestEnvironment } from './test-utils'
import { setupTestEnvironment } from './test-utils.tsx'
// Setup global test environment
setupTestEnvironment()

View File

@ -2,6 +2,7 @@ import React from 'react'
import { render, RenderOptions } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom'
import { vi } from 'vitest'
import { NotificationProvider } from '../contexts/NotificationContext'
interface User {
id: string
@ -38,12 +39,46 @@ export const createMockAdminUser = (overrides: Partial<User> = {}): User => ({
// Centralized API mocking to eliminate per-file duplication
export const createMockApiServices = () => {
const mockDocumentService = {
enhancedSearch: vi.fn().mockResolvedValue({ documents: [], total: 0 }),
enhancedSearch: vi.fn().mockResolvedValue({
data: {
documents: [],
total: 0,
query_time_ms: 0,
suggestions: []
}
}),
search: vi.fn().mockResolvedValue({
data: {
documents: [],
total: 0,
query_time_ms: 0,
suggestions: []
}
}),
bulkRetryOcr: vi.fn().mockResolvedValue({ success: true }),
getDocument: vi.fn().mockResolvedValue({}),
uploadDocument: vi.fn().mockResolvedValue({}),
deleteDocument: vi.fn().mockResolvedValue({}),
updateDocument: vi.fn().mockResolvedValue({}),
getById: vi.fn().mockResolvedValue({ data: {} }),
upload: vi.fn().mockResolvedValue({ data: {} }),
list: vi.fn().mockResolvedValue({ data: [] }),
listWithPagination: vi.fn().mockResolvedValue({ data: { documents: [], pagination: { total: 0, limit: 20, offset: 0, has_more: false } } }),
delete: vi.fn().mockResolvedValue({}),
bulkDelete: vi.fn().mockResolvedValue({}),
retryOcr: vi.fn().mockResolvedValue({}),
getFacets: vi.fn().mockResolvedValue({ data: { mime_types: [], tags: [] } }),
getOcrText: vi.fn().mockResolvedValue({ data: {} }),
download: vi.fn().mockResolvedValue({ data: new Blob() }),
getFailedOcrDocuments: vi.fn().mockResolvedValue({ data: [] }),
getFailedDocuments: vi.fn().mockResolvedValue({ data: [] }),
deleteLowConfidence: vi.fn().mockResolvedValue({ data: {} }),
deleteFailedOcr: vi.fn().mockResolvedValue({ data: {} }),
view: vi.fn().mockResolvedValue({ data: new Blob() }),
getThumbnail: vi.fn().mockResolvedValue({ data: new Blob() }),
getProcessedImage: vi.fn().mockResolvedValue({ data: new Blob() }),
downloadFile: vi.fn().mockResolvedValue(undefined),
getDuplicates: vi.fn().mockResolvedValue({ data: [] }),
getRetryStats: vi.fn().mockResolvedValue({ data: {} }),
getRetryRecommendations: vi.fn().mockResolvedValue({ data: [] }),
getDocumentRetryHistory: vi.fn().mockResolvedValue({ data: [] }),
}
const mockAuthService = {
@ -77,24 +112,11 @@ export const createMockApiServices = () => {
}
// Setup global API mocks (call this in setup files)
// Note: Individual test files should handle their own vi.mock() calls
export const setupApiMocks = () => {
const mockServices = createMockApiServices()
vi.mock('../../services/api', () => ({
documentService: mockServices.documentService,
authService: mockServices.authService,
sourceService: mockServices.sourceService,
labelService: mockServices.labelService,
api: {
defaults: {
headers: {
common: {}
}
}
}
}))
return mockServices
// Just return the mock services for use in tests
// The actual vi.mock() should be done in individual test files
return createMockApiServices()
}
// Create a mock AuthProvider for testing
@ -136,9 +158,11 @@ const AllTheProviders = ({
}) => {
return (
<BrowserRouter {...routerProps}>
<MockAuthProvider mockValues={authValues}>
{children}
</MockAuthProvider>
<NotificationProvider>
<MockAuthProvider mockValues={authValues}>
{children}
</MockAuthProvider>
</NotificationProvider>
</BrowserRouter>
)
}