dify/web/app/components/datasets/common/document-picker/index.spec.tsx

1044 lines
30 KiB
TypeScript

import type { ParentMode, SimpleDocumentDetail } from '@/models/datasets'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { fireEvent, render, screen } from '@testing-library/react'
import * as React from 'react'
import { ChunkingMode, DataSourceType } from '@/models/datasets'
import DocumentPicker from './index'
// Mock portal-to-follow-elem - always render content for testing
vi.mock('@/app/components/base/portal-to-follow-elem', () => ({
PortalToFollowElem: ({ children, open }: {
children: React.ReactNode
open?: boolean
}) => (
<div data-testid="portal-elem" data-open={String(open || false)}>
{children}
</div>
),
PortalToFollowElemTrigger: ({ children, onClick }: {
children: React.ReactNode
onClick?: () => void
}) => (
<div data-testid="portal-trigger" onClick={onClick}>
{children}
</div>
),
// Always render content to allow testing document selection
PortalToFollowElemContent: ({ children, className }: {
children: React.ReactNode
className?: string
}) => (
<div data-testid="portal-content" className={className}>
{children}
</div>
),
}))
// Mock useDocumentList hook with controllable return value
let mockDocumentListData: { data: SimpleDocumentDetail[] } | undefined
let mockDocumentListLoading = false
const { mockUseDocumentList } = vi.hoisted(() => ({
mockUseDocumentList: vi.fn(),
}))
// Set up the implementation after variables are defined
mockUseDocumentList.mockImplementation(() => ({
data: mockDocumentListLoading ? undefined : mockDocumentListData,
isLoading: mockDocumentListLoading,
}))
vi.mock('@/service/knowledge/use-document', () => ({
useDocumentList: mockUseDocumentList,
}))
// Mock icons - mock all remixicon components used in the component tree
vi.mock('@remixicon/react', () => ({
RiArrowDownSLine: () => <span data-testid="arrow-icon"></span>,
RiFile3Fill: () => <span data-testid="file-icon">📄</span>,
RiFileCodeFill: () => <span data-testid="file-code-icon">📄</span>,
RiFileExcelFill: () => <span data-testid="file-excel-icon">📄</span>,
RiFileGifFill: () => <span data-testid="file-gif-icon">📄</span>,
RiFileImageFill: () => <span data-testid="file-image-icon">📄</span>,
RiFileMusicFill: () => <span data-testid="file-music-icon">📄</span>,
RiFilePdf2Fill: () => <span data-testid="file-pdf-icon">📄</span>,
RiFilePpt2Fill: () => <span data-testid="file-ppt-icon">📄</span>,
RiFileTextFill: () => <span data-testid="file-text-icon">📄</span>,
RiFileVideoFill: () => <span data-testid="file-video-icon">📄</span>,
RiFileWordFill: () => <span data-testid="file-word-icon">📄</span>,
RiMarkdownFill: () => <span data-testid="file-markdown-icon">📄</span>,
RiSearchLine: () => <span data-testid="search-icon">🔍</span>,
RiCloseLine: () => <span data-testid="close-icon"></span>,
}))
// Factory function to create mock SimpleDocumentDetail
const createMockDocument = (overrides: Partial<SimpleDocumentDetail> = {}): SimpleDocumentDetail => ({
id: `doc-${Math.random().toString(36).substr(2, 9)}`,
batch: 'batch-1',
position: 1,
dataset_id: 'dataset-1',
data_source_type: DataSourceType.FILE,
data_source_info: {
upload_file: {
id: 'file-1',
name: 'test-file.txt',
size: 1024,
extension: 'txt',
mime_type: 'text/plain',
created_by: 'user-1',
created_at: Date.now(),
},
// Required fields for LegacyDataSourceInfo
job_id: 'job-1',
url: '',
},
dataset_process_rule_id: 'rule-1',
name: 'Test Document',
created_from: 'web',
created_by: 'user-1',
created_at: Date.now(),
indexing_status: 'completed',
display_status: 'enabled',
doc_form: ChunkingMode.text,
doc_language: 'en',
enabled: true,
word_count: 1000,
archived: false,
updated_at: Date.now(),
hit_count: 0,
data_source_detail_dict: {
upload_file: {
name: 'test-file.txt',
extension: 'txt',
},
},
...overrides,
})
// Factory function to create multiple documents
const createMockDocumentList = (count: number): SimpleDocumentDetail[] => {
return Array.from({ length: count }, (_, index) =>
createMockDocument({
id: `doc-${index + 1}`,
name: `Document ${index + 1}`,
data_source_detail_dict: {
upload_file: {
name: `document-${index + 1}.pdf`,
extension: 'pdf',
},
},
}))
}
// Factory function to create props
const createDefaultProps = (overrides: Partial<React.ComponentProps<typeof DocumentPicker>> = {}) => ({
datasetId: 'dataset-1',
value: {
name: 'Test Document',
extension: 'txt',
chunkingMode: ChunkingMode.text,
parentMode: undefined as ParentMode | undefined,
},
onChange: vi.fn(),
...overrides,
})
// Create a new QueryClient for each test
const createTestQueryClient = () =>
new QueryClient({
defaultOptions: {
queries: {
retry: false,
gcTime: 0,
staleTime: 0,
},
},
})
// Helper to render component with providers
const renderComponent = (props: Partial<React.ComponentProps<typeof DocumentPicker>> = {}) => {
const queryClient = createTestQueryClient()
const defaultProps = createDefaultProps(props)
return {
...render(
<QueryClientProvider client={queryClient}>
<DocumentPicker {...defaultProps} />
</QueryClientProvider>,
),
queryClient,
props: defaultProps,
}
}
describe('DocumentPicker', () => {
beforeEach(() => {
vi.clearAllMocks()
// Reset mock state
mockDocumentListData = { data: createMockDocumentList(5) }
mockDocumentListLoading = false
})
// Tests for basic rendering
describe('Rendering', () => {
it('should render without crashing', () => {
renderComponent()
expect(screen.getByTestId('portal-elem')).toBeInTheDocument()
})
it('should render document name when provided', () => {
renderComponent({
value: {
name: 'My Document',
extension: 'pdf',
chunkingMode: ChunkingMode.text,
},
})
expect(screen.getByText('My Document')).toBeInTheDocument()
})
it('should render placeholder when name is not provided', () => {
renderComponent({
value: {
name: undefined,
extension: 'pdf',
chunkingMode: ChunkingMode.text,
},
})
expect(screen.getByText('--')).toBeInTheDocument()
})
it('should render arrow icon', () => {
renderComponent()
expect(screen.getByTestId('arrow-icon')).toBeInTheDocument()
})
it('should render general mode label', () => {
renderComponent({
value: {
name: 'Test',
extension: 'txt',
chunkingMode: ChunkingMode.text,
},
})
expect(screen.getByText('dataset.chunkingMode.general')).toBeInTheDocument()
})
it('should render QA mode label', () => {
renderComponent({
value: {
name: 'Test',
extension: 'txt',
chunkingMode: ChunkingMode.qa,
},
})
expect(screen.getByText('dataset.chunkingMode.qa')).toBeInTheDocument()
})
it('should render parentChild mode label with paragraph parent mode', () => {
renderComponent({
value: {
name: 'Test',
extension: 'txt',
chunkingMode: ChunkingMode.parentChild,
parentMode: 'paragraph',
},
})
expect(screen.getByText(/dataset.chunkingMode.parentChild/)).toBeInTheDocument()
expect(screen.getByText(/dataset.parentMode.paragraph/)).toBeInTheDocument()
})
it('should render parentChild mode label with full-doc parent mode', () => {
renderComponent({
value: {
name: 'Test',
extension: 'txt',
chunkingMode: ChunkingMode.parentChild,
parentMode: 'full-doc',
},
})
expect(screen.getByText(/dataset.chunkingMode.parentChild/)).toBeInTheDocument()
expect(screen.getByText(/dataset.parentMode.fullDoc/)).toBeInTheDocument()
})
it('should render placeholder for parentMode when not provided', () => {
renderComponent({
value: {
name: 'Test',
extension: 'txt',
chunkingMode: ChunkingMode.parentChild,
parentMode: undefined,
},
})
// parentModeLabel should be '--' when parentMode is not provided
expect(screen.getByText(/--/)).toBeInTheDocument()
})
})
// Tests for props handling
describe('Props', () => {
it('should accept required props', () => {
const onChange = vi.fn()
renderComponent({
datasetId: 'test-dataset',
value: {
name: 'Test',
extension: 'txt',
chunkingMode: ChunkingMode.text,
},
onChange,
})
expect(screen.getByTestId('portal-elem')).toBeInTheDocument()
})
it('should handle value with all fields', () => {
renderComponent({
value: {
name: 'Full Document',
extension: 'docx',
chunkingMode: ChunkingMode.parentChild,
parentMode: 'paragraph',
},
})
expect(screen.getByText('Full Document')).toBeInTheDocument()
})
it('should handle value with minimal fields', () => {
renderComponent({
value: {
name: undefined,
extension: undefined,
chunkingMode: undefined,
parentMode: undefined,
},
})
expect(screen.getByText('--')).toBeInTheDocument()
})
it('should pass datasetId to mockUseDocumentList hook', () => {
renderComponent({ datasetId: 'custom-dataset-id' })
expect(mockUseDocumentList).toHaveBeenCalledWith(
expect.objectContaining({
datasetId: 'custom-dataset-id',
}),
)
})
})
// Tests for state management and updates
describe('State Management', () => {
it('should initialize with popup closed', () => {
renderComponent()
expect(screen.getByTestId('portal-elem')).toHaveAttribute('data-open', 'false')
})
it('should open popup when trigger is clicked', () => {
renderComponent()
const trigger = screen.getByTestId('portal-trigger')
fireEvent.click(trigger)
// Verify click handler is called
expect(trigger).toBeInTheDocument()
})
it('should maintain search query state', async () => {
renderComponent()
// Initial call should have empty keyword
expect(mockUseDocumentList).toHaveBeenCalledWith(
expect.objectContaining({
query: expect.objectContaining({
keyword: '',
}),
}),
)
})
it('should update query when search input changes', () => {
renderComponent()
// Verify the component uses mockUseDocumentList with query parameter
expect(mockUseDocumentList).toHaveBeenCalledWith(
expect.objectContaining({
query: expect.objectContaining({
keyword: '',
}),
}),
)
})
})
// Tests for callback stability and memoization
describe('Callback Stability', () => {
it('should maintain stable onChange callback when value changes', () => {
const onChange = vi.fn()
const value1 = {
name: 'Doc 1',
extension: 'txt',
chunkingMode: ChunkingMode.text,
}
const value2 = {
name: 'Doc 2',
extension: 'pdf',
chunkingMode: ChunkingMode.text,
}
const queryClient = createTestQueryClient()
const { rerender } = render(
<QueryClientProvider client={queryClient}>
<DocumentPicker
datasetId="dataset-1"
value={value1}
onChange={onChange}
/>
</QueryClientProvider>,
)
rerender(
<QueryClientProvider client={queryClient}>
<DocumentPicker
datasetId="dataset-1"
value={value2}
onChange={onChange}
/>
</QueryClientProvider>,
)
// Component should still render correctly after rerender
expect(screen.getByText('Doc 2')).toBeInTheDocument()
})
it('should use updated onChange callback after rerender', () => {
const onChange1 = vi.fn()
const onChange2 = vi.fn()
const value = {
name: 'Test Doc',
extension: 'txt',
chunkingMode: ChunkingMode.text,
}
const queryClient = createTestQueryClient()
const { rerender } = render(
<QueryClientProvider client={queryClient}>
<DocumentPicker
datasetId="dataset-1"
value={value}
onChange={onChange1}
/>
</QueryClientProvider>,
)
rerender(
<QueryClientProvider client={queryClient}>
<DocumentPicker
datasetId="dataset-1"
value={value}
onChange={onChange2}
/>
</QueryClientProvider>,
)
// The component should use the new callback
expect(screen.getByTestId('portal-elem')).toBeInTheDocument()
})
it('should memoize handleChange callback with useCallback', () => {
// The handleChange callback is created with useCallback and depends on
// documentsList, onChange, and setOpen
const onChange = vi.fn()
renderComponent({ onChange })
// Verify component renders correctly, callback memoization is internal
expect(screen.getByTestId('portal-elem')).toBeInTheDocument()
})
})
// Tests for memoization logic and dependencies
describe('Memoization Logic', () => {
it('should be wrapped with React.memo', () => {
// React.memo components have a $$typeof property
expect((DocumentPicker as any).$$typeof).toBeDefined()
})
it('should compute parentModeLabel correctly with useMemo', () => {
// Test paragraph mode
renderComponent({
value: {
name: 'Test',
extension: 'txt',
chunkingMode: ChunkingMode.parentChild,
parentMode: 'paragraph',
},
})
expect(screen.getByText(/dataset.parentMode.paragraph/)).toBeInTheDocument()
})
it('should update parentModeLabel when parentMode changes', () => {
// Test full-doc mode
renderComponent({
value: {
name: 'Test',
extension: 'txt',
chunkingMode: ChunkingMode.parentChild,
parentMode: 'full-doc',
},
})
expect(screen.getByText(/dataset.parentMode.fullDoc/)).toBeInTheDocument()
})
it('should not re-render when props are the same', () => {
const onChange = vi.fn()
const value = {
name: 'Stable Doc',
extension: 'txt',
chunkingMode: ChunkingMode.text,
}
const queryClient = createTestQueryClient()
const { rerender } = render(
<QueryClientProvider client={queryClient}>
<DocumentPicker
datasetId="dataset-1"
value={value}
onChange={onChange}
/>
</QueryClientProvider>,
)
// Rerender with same props reference
rerender(
<QueryClientProvider client={queryClient}>
<DocumentPicker
datasetId="dataset-1"
value={value}
onChange={onChange}
/>
</QueryClientProvider>,
)
expect(screen.getByText('Stable Doc')).toBeInTheDocument()
})
})
// Tests for user interactions and event handlers
describe('User Interactions', () => {
it('should toggle popup when trigger is clicked', () => {
renderComponent()
const trigger = screen.getByTestId('portal-trigger')
fireEvent.click(trigger)
// Trigger click should be handled
expect(trigger).toBeInTheDocument()
})
it('should handle document selection when popup is open', () => {
// Test the handleChange callback logic
const onChange = vi.fn()
const mockDocs = createMockDocumentList(3)
mockDocumentListData = { data: mockDocs }
renderComponent({ onChange })
// The handleChange callback should find the document and call onChange
// We can verify this by checking that mockUseDocumentList was called
expect(mockUseDocumentList).toHaveBeenCalled()
})
it('should handle search input change', () => {
renderComponent()
// The search input is only visible when popup is open
// We verify that the component initializes with empty query
expect(mockUseDocumentList).toHaveBeenCalledWith(
expect.objectContaining({
query: expect.objectContaining({
keyword: '',
}),
}),
)
})
it('should initialize with default query parameters', () => {
renderComponent()
expect(mockUseDocumentList).toHaveBeenCalledWith(
expect.objectContaining({
query: {
keyword: '',
page: 1,
limit: 20,
},
}),
)
})
})
// Tests for API calls
describe('API Calls', () => {
it('should call mockUseDocumentList with correct parameters', () => {
renderComponent({ datasetId: 'test-dataset-123' })
expect(mockUseDocumentList).toHaveBeenCalledWith({
datasetId: 'test-dataset-123',
query: {
keyword: '',
page: 1,
limit: 20,
},
})
})
it('should handle loading state', () => {
mockDocumentListLoading = true
mockDocumentListData = undefined
renderComponent()
// When loading, component should still render without crashing
expect(screen.getByTestId('portal-elem')).toBeInTheDocument()
})
it('should fetch documents on mount', () => {
mockDocumentListLoading = false
mockDocumentListData = { data: createMockDocumentList(3) }
renderComponent()
// Verify the hook was called
expect(mockUseDocumentList).toHaveBeenCalled()
})
it('should handle empty document list', () => {
mockDocumentListData = { data: [] }
renderComponent()
// Component should render without crashing
expect(screen.getByTestId('portal-elem')).toBeInTheDocument()
})
it('should handle undefined data response', () => {
mockDocumentListData = undefined
renderComponent()
// Should not crash
expect(screen.getByTestId('portal-elem')).toBeInTheDocument()
})
})
// Tests for component memoization
describe('Component Memoization', () => {
it('should export as React.memo wrapped component', () => {
// Check that the component is memoized
expect(DocumentPicker).toBeDefined()
expect(typeof DocumentPicker).toBe('object') // React.memo returns an object
})
it('should preserve render output when datasetId is the same', () => {
const queryClient = createTestQueryClient()
const value = {
name: 'Memo Test',
extension: 'txt',
chunkingMode: ChunkingMode.text,
}
const onChange = vi.fn()
const { rerender } = render(
<QueryClientProvider client={queryClient}>
<DocumentPicker
datasetId="same-dataset"
value={value}
onChange={onChange}
/>
</QueryClientProvider>,
)
expect(screen.getByText('Memo Test')).toBeInTheDocument()
rerender(
<QueryClientProvider client={queryClient}>
<DocumentPicker
datasetId="same-dataset"
value={value}
onChange={onChange}
/>
</QueryClientProvider>,
)
expect(screen.getByText('Memo Test')).toBeInTheDocument()
})
})
// Tests for edge cases and error handling
describe('Edge Cases', () => {
it('should handle null name', () => {
renderComponent({
value: {
name: undefined,
extension: 'txt',
chunkingMode: ChunkingMode.text,
},
})
expect(screen.getByText('--')).toBeInTheDocument()
})
it('should handle empty string name', () => {
renderComponent({
value: {
name: '',
extension: 'txt',
chunkingMode: ChunkingMode.text,
},
})
// Empty string is falsy, so should show '--'
expect(screen.queryByText('--')).toBeInTheDocument()
})
it('should handle undefined extension', () => {
renderComponent({
value: {
name: 'Test Doc',
extension: undefined,
chunkingMode: ChunkingMode.text,
},
})
// Should not crash
expect(screen.getByText('Test Doc')).toBeInTheDocument()
})
it('should handle undefined chunkingMode', () => {
renderComponent({
value: {
name: 'Test Doc',
extension: 'txt',
chunkingMode: undefined,
},
})
// When chunkingMode is undefined, none of the mode conditions are true
expect(screen.getByText('Test Doc')).toBeInTheDocument()
})
it('should handle document without data_source_detail_dict', () => {
const docWithoutDetail = createMockDocument({
id: 'doc-no-detail',
name: 'Doc Without Detail',
data_source_detail_dict: undefined,
})
mockDocumentListData = { data: [docWithoutDetail] }
// Component should handle mapping documents even without data_source_detail_dict
renderComponent()
// Should not crash
expect(screen.getByTestId('portal-elem')).toBeInTheDocument()
})
it('should handle rapid toggle clicks', () => {
renderComponent()
const trigger = screen.getByTestId('portal-trigger')
// Rapid clicks
fireEvent.click(trigger)
fireEvent.click(trigger)
fireEvent.click(trigger)
fireEvent.click(trigger)
// Should not crash
expect(trigger).toBeInTheDocument()
})
it('should handle very long document names in trigger', () => {
const longName = 'A'.repeat(500)
renderComponent({
value: {
name: longName,
extension: 'txt',
chunkingMode: ChunkingMode.text,
},
})
// Should render long name without crashing
expect(screen.getByText(longName)).toBeInTheDocument()
})
it('should handle special characters in document name', () => {
const specialName = '<script>alert("xss")</script>'
renderComponent({
value: {
name: specialName,
extension: 'txt',
chunkingMode: ChunkingMode.text,
},
})
// React should escape the text
expect(screen.getByText(specialName)).toBeInTheDocument()
})
it('should handle documents with missing extension in data_source_detail_dict', () => {
const docWithEmptyExtension = createMockDocument({
id: 'doc-empty-ext',
name: 'Doc Empty Ext',
data_source_detail_dict: {
upload_file: {
name: 'file-no-ext',
extension: '',
},
},
})
mockDocumentListData = { data: [docWithEmptyExtension] }
// Component should handle mapping documents with empty extension
renderComponent()
// Should not crash
expect(screen.getByTestId('portal-elem')).toBeInTheDocument()
})
it('should handle document list mapping with various data_source_detail_dict states', () => {
// Test the mapping logic: d.data_source_detail_dict?.upload_file?.extension || ''
const docs = [
createMockDocument({
id: 'doc-1',
name: 'With Extension',
data_source_detail_dict: {
upload_file: { name: 'file.pdf', extension: 'pdf' },
},
}),
createMockDocument({
id: 'doc-2',
name: 'Without Detail Dict',
data_source_detail_dict: undefined,
}),
]
mockDocumentListData = { data: docs }
renderComponent()
// Should not crash during mapping
expect(screen.getByTestId('portal-elem')).toBeInTheDocument()
})
})
// Tests for all prop variations
describe('Prop Variations', () => {
describe('datasetId variations', () => {
it('should handle empty datasetId', () => {
renderComponent({ datasetId: '' })
expect(screen.getByTestId('portal-elem')).toBeInTheDocument()
})
it('should handle UUID format datasetId', () => {
renderComponent({ datasetId: '123e4567-e89b-12d3-a456-426614174000' })
expect(screen.getByTestId('portal-elem')).toBeInTheDocument()
})
})
describe('value.chunkingMode variations', () => {
const chunkingModes = [
{ mode: ChunkingMode.text, label: 'dataset.chunkingMode.general' },
{ mode: ChunkingMode.qa, label: 'dataset.chunkingMode.qa' },
{ mode: ChunkingMode.parentChild, label: 'dataset.chunkingMode.parentChild' },
]
it.each(chunkingModes)(
'should display correct label for $mode mode',
({ mode, label }) => {
renderComponent({
value: {
name: 'Test',
extension: 'txt',
chunkingMode: mode,
parentMode: mode === ChunkingMode.parentChild ? 'paragraph' : undefined,
},
})
expect(screen.getByText(new RegExp(label))).toBeInTheDocument()
},
)
})
describe('value.parentMode variations', () => {
const parentModes: Array<{ mode: ParentMode, label: string }> = [
{ mode: 'paragraph', label: 'dataset.parentMode.paragraph' },
{ mode: 'full-doc', label: 'dataset.parentMode.fullDoc' },
]
it.each(parentModes)(
'should display correct label for $mode parentMode',
({ mode, label }) => {
renderComponent({
value: {
name: 'Test',
extension: 'txt',
chunkingMode: ChunkingMode.parentChild,
parentMode: mode,
},
})
expect(screen.getByText(new RegExp(label))).toBeInTheDocument()
},
)
})
describe('value.extension variations', () => {
const extensions = ['txt', 'pdf', 'docx', 'xlsx', 'csv', 'md', 'html']
it.each(extensions)('should handle %s extension', (ext) => {
renderComponent({
value: {
name: `File.${ext}`,
extension: ext,
chunkingMode: ChunkingMode.text,
},
})
expect(screen.getByText(`File.${ext}`)).toBeInTheDocument()
})
})
})
// Tests for document selection
describe('Document Selection', () => {
it('should fetch documents list via mockUseDocumentList', () => {
const mockDoc = createMockDocument({
id: 'selected-doc',
name: 'Selected Document',
})
mockDocumentListData = { data: [mockDoc] }
const onChange = vi.fn()
renderComponent({ onChange })
// Verify the hook was called
expect(mockUseDocumentList).toHaveBeenCalled()
})
it('should call onChange when document is selected', () => {
const docs = createMockDocumentList(3)
mockDocumentListData = { data: docs }
const onChange = vi.fn()
renderComponent({ onChange })
// Click on a document in the list
fireEvent.click(screen.getByText('Document 2'))
// handleChange should find the document and call onChange with full document
expect(onChange).toHaveBeenCalledTimes(1)
expect(onChange).toHaveBeenCalledWith(docs[1])
})
it('should map document list items correctly', () => {
const docs = createMockDocumentList(3)
mockDocumentListData = { data: docs }
renderComponent()
// Documents should be rendered in the list
expect(screen.getByText('Document 1')).toBeInTheDocument()
expect(screen.getByText('Document 2')).toBeInTheDocument()
expect(screen.getByText('Document 3')).toBeInTheDocument()
})
})
// Tests for integration with child components
describe('Child Component Integration', () => {
it('should pass correct data to DocumentList when popup is open', () => {
const docs = createMockDocumentList(3)
mockDocumentListData = { data: docs }
renderComponent()
// DocumentList receives mapped documents: { id, name, extension }
// We verify the data is fetched
expect(mockUseDocumentList).toHaveBeenCalled()
})
it('should map document data_source_detail_dict extension correctly', () => {
const doc = createMockDocument({
id: 'mapped-doc',
name: 'Mapped Document',
data_source_detail_dict: {
upload_file: {
name: 'mapped.pdf',
extension: 'pdf',
},
},
})
mockDocumentListData = { data: [doc] }
renderComponent()
// The mapping: d.data_source_detail_dict?.upload_file?.extension || ''
// Should extract 'pdf' from the document
expect(screen.getByTestId('portal-elem')).toBeInTheDocument()
})
it('should render trigger with SearchInput integration', () => {
renderComponent()
// The trigger is always rendered
expect(screen.getByTestId('portal-trigger')).toBeInTheDocument()
})
it('should integrate FileIcon component', () => {
// Use empty document list to avoid duplicate icons from list
mockDocumentListData = { data: [] }
renderComponent({
value: {
name: 'test.pdf',
extension: 'pdf',
chunkingMode: ChunkingMode.text,
},
})
// FileIcon should be rendered via DocumentFileIcon - pdf renders pdf icon
expect(screen.getByTestId('file-pdf-icon')).toBeInTheDocument()
})
})
// Tests for visual states
describe('Visual States', () => {
it('should render portal content for document selection', () => {
renderComponent()
// Portal content is rendered in our mock for testing
expect(screen.getByTestId('portal-content')).toBeInTheDocument()
})
})
})