296 lines
8.5 KiB
TypeScript
296 lines
8.5 KiB
TypeScript
import React from 'react'
|
|
import { render, RenderOptions } from '@testing-library/react'
|
|
import { BrowserRouter } from 'react-router-dom'
|
|
import { vi } from 'vitest'
|
|
import { I18nextProvider } from 'react-i18next'
|
|
import i18n from 'i18next'
|
|
import { initReactI18next } from 'react-i18next'
|
|
import { NotificationProvider } from '../contexts/NotificationContext'
|
|
|
|
// Initialize i18n for tests
|
|
i18n
|
|
.use(initReactI18next)
|
|
.init({
|
|
lng: 'en',
|
|
fallbackLng: 'en',
|
|
ns: ['translation'],
|
|
defaultNS: 'translation',
|
|
resources: {
|
|
en: {
|
|
translation: require('../../public/locales/en/translation.json')
|
|
}
|
|
},
|
|
interpolation: {
|
|
escapeValue: false
|
|
},
|
|
react: {
|
|
useSuspense: false
|
|
}
|
|
})
|
|
|
|
interface User {
|
|
id: string
|
|
username: string
|
|
email: string
|
|
role?: string
|
|
}
|
|
|
|
interface MockAuthContextType {
|
|
user: User | null
|
|
loading: boolean
|
|
login: (username: string, password: string) => Promise<void>
|
|
register: (username: string, email: string, password: string) => Promise<void>
|
|
logout: () => void
|
|
}
|
|
|
|
// Test data factories for consistent mock data across tests
|
|
export const createMockUser = (overrides: Partial<User> = {}): User => ({
|
|
id: '1',
|
|
username: 'testuser',
|
|
email: 'test@example.com',
|
|
role: 'user',
|
|
...overrides
|
|
})
|
|
|
|
export const createMockAdminUser = (overrides: Partial<User> = {}): User => ({
|
|
id: '2',
|
|
username: 'adminuser',
|
|
email: 'admin@example.com',
|
|
role: 'admin',
|
|
...overrides
|
|
})
|
|
|
|
// Centralized API mocking to eliminate per-file duplication
|
|
export const createMockApiServices = () => {
|
|
const mockDocumentService = {
|
|
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({}),
|
|
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 = {
|
|
login: vi.fn().mockResolvedValue({ token: 'mock-token', user: createMockUser() }),
|
|
register: vi.fn().mockResolvedValue({ token: 'mock-token', user: createMockUser() }),
|
|
logout: vi.fn().mockResolvedValue({}),
|
|
getCurrentUser: vi.fn().mockResolvedValue(createMockUser()),
|
|
}
|
|
|
|
const mockSourceService = {
|
|
getSources: vi.fn().mockResolvedValue([]),
|
|
createSource: vi.fn().mockResolvedValue({}),
|
|
updateSource: vi.fn().mockResolvedValue({}),
|
|
deleteSource: vi.fn().mockResolvedValue({}),
|
|
syncSource: vi.fn().mockResolvedValue({}),
|
|
}
|
|
|
|
const mockLabelService = {
|
|
getLabels: vi.fn().mockResolvedValue([]),
|
|
createLabel: vi.fn().mockResolvedValue({}),
|
|
updateLabel: vi.fn().mockResolvedValue({}),
|
|
deleteLabel: vi.fn().mockResolvedValue({}),
|
|
}
|
|
|
|
return {
|
|
documentService: mockDocumentService,
|
|
authService: mockAuthService,
|
|
sourceService: mockSourceService,
|
|
labelService: mockLabelService,
|
|
}
|
|
}
|
|
|
|
// Setup global API mocks (call this in setup files)
|
|
// Note: Individual test files should handle their own vi.mock() calls
|
|
export const setupApiMocks = () => {
|
|
// 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
|
|
export const MockAuthProvider = ({
|
|
children,
|
|
mockValues = {}
|
|
}: {
|
|
children: React.ReactNode
|
|
mockValues?: Partial<MockAuthContextType>
|
|
}) => {
|
|
const defaultMocks = {
|
|
user: null,
|
|
loading: false,
|
|
login: vi.fn(),
|
|
register: vi.fn(),
|
|
logout: vi.fn(),
|
|
...mockValues
|
|
}
|
|
|
|
// Mock the useAuth hook
|
|
const AuthContext = React.createContext(defaultMocks)
|
|
|
|
return (
|
|
<AuthContext.Provider value={defaultMocks}>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
)
|
|
}
|
|
|
|
// Enhanced provider wrapper with theme and notification contexts
|
|
const AllTheProviders = ({
|
|
children,
|
|
authValues,
|
|
routerProps = {}
|
|
}: {
|
|
children: React.ReactNode
|
|
authValues?: Partial<MockAuthContextType>
|
|
routerProps?: any
|
|
}) => {
|
|
return (
|
|
<I18nextProvider i18n={i18n}>
|
|
<BrowserRouter {...routerProps}>
|
|
<NotificationProvider>
|
|
<MockAuthProvider mockValues={authValues}>
|
|
{children}
|
|
</MockAuthProvider>
|
|
</NotificationProvider>
|
|
</BrowserRouter>
|
|
</I18nextProvider>
|
|
)
|
|
}
|
|
|
|
// Enhanced render functions with better provider configuration
|
|
export const renderWithProviders = (
|
|
ui: React.ReactElement,
|
|
options?: Omit<RenderOptions, 'wrapper'> & {
|
|
authValues?: Partial<MockAuthContextType>
|
|
routerProps?: any
|
|
}
|
|
) => {
|
|
const { authValues, routerProps, ...renderOptions } = options || {}
|
|
|
|
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
<AllTheProviders authValues={authValues} routerProps={routerProps}>
|
|
{children}
|
|
</AllTheProviders>
|
|
)
|
|
|
|
return render(ui, { wrapper: Wrapper, ...renderOptions })
|
|
}
|
|
|
|
export const renderWithMockAuth = (
|
|
ui: React.ReactElement,
|
|
mockAuthValues?: Partial<MockAuthContextType>,
|
|
options?: Omit<RenderOptions, 'wrapper'>
|
|
) => {
|
|
return renderWithProviders(ui, { ...options, authValues: mockAuthValues })
|
|
}
|
|
|
|
// Render with authenticated user (commonly used pattern)
|
|
export const renderWithAuthenticatedUser = (
|
|
ui: React.ReactElement,
|
|
user: User = createMockUser(),
|
|
options?: Omit<RenderOptions, 'wrapper'>
|
|
) => {
|
|
return renderWithProviders(ui, {
|
|
...options,
|
|
authValues: {
|
|
user,
|
|
loading: false,
|
|
login: vi.fn(),
|
|
register: vi.fn(),
|
|
logout: vi.fn(),
|
|
}
|
|
})
|
|
}
|
|
|
|
// Render with admin user (commonly used pattern)
|
|
export const renderWithAdminUser = (
|
|
ui: React.ReactElement,
|
|
options?: Omit<RenderOptions, 'wrapper'>
|
|
) => {
|
|
return renderWithAuthenticatedUser(ui, createMockAdminUser(), options)
|
|
}
|
|
|
|
// Mock localStorage consistently across tests
|
|
export const createMockLocalStorage = () => {
|
|
const storage: Record<string, string> = {}
|
|
|
|
return {
|
|
getItem: vi.fn((key: string) => storage[key] || null),
|
|
setItem: vi.fn((key: string, value: string) => { storage[key] = value }),
|
|
removeItem: vi.fn((key: string) => { delete storage[key] }),
|
|
clear: vi.fn(() => Object.keys(storage).forEach(key => delete storage[key])),
|
|
key: vi.fn((index: number) => Object.keys(storage)[index] || null),
|
|
length: Object.keys(storage).length,
|
|
}
|
|
}
|
|
|
|
// Setup function to be called in test setup files
|
|
export const setupTestEnvironment = () => {
|
|
// Mock localStorage
|
|
Object.defineProperty(window, 'localStorage', {
|
|
value: createMockLocalStorage(),
|
|
writable: true,
|
|
})
|
|
|
|
// Mock sessionStorage
|
|
Object.defineProperty(window, 'sessionStorage', {
|
|
value: createMockLocalStorage(),
|
|
writable: true,
|
|
})
|
|
|
|
// Mock window.matchMedia
|
|
Object.defineProperty(window, 'matchMedia', {
|
|
writable: true,
|
|
value: vi.fn().mockImplementation(query => ({
|
|
matches: false,
|
|
media: query,
|
|
onchange: null,
|
|
addListener: vi.fn(),
|
|
removeListener: vi.fn(),
|
|
addEventListener: vi.fn(),
|
|
removeEventListener: vi.fn(),
|
|
dispatchEvent: vi.fn(),
|
|
})),
|
|
})
|
|
|
|
return setupApiMocks()
|
|
}
|
|
|
|
// re-export everything
|
|
export * from '@testing-library/react' |