mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 10:28:10 +08:00
test: add comprehensive unit and integration tests for dataset module (#32187)
Co-authored-by: CodingOnStar <hanxujiang@dify.com> Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -0,0 +1,49 @@
|
||||
import type { DocumentItem } from '@/models/datasets'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import DocumentList from '../document-list'
|
||||
|
||||
vi.mock('../../document-file-icon', () => ({
|
||||
default: ({ name, extension }: { name?: string, extension?: string }) => (
|
||||
<span data-testid="file-icon">
|
||||
{name}
|
||||
.
|
||||
{extension}
|
||||
</span>
|
||||
),
|
||||
}))
|
||||
|
||||
describe('DocumentList', () => {
|
||||
const mockList = [
|
||||
{ id: 'doc-1', name: 'report', extension: 'pdf' },
|
||||
{ id: 'doc-2', name: 'data', extension: 'csv' },
|
||||
] as DocumentItem[]
|
||||
|
||||
const onChange = vi.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('should render all documents', () => {
|
||||
render(<DocumentList list={mockList} onChange={onChange} />)
|
||||
expect(screen.getByText('report')).toBeInTheDocument()
|
||||
expect(screen.getByText('data')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render file icons', () => {
|
||||
render(<DocumentList list={mockList} onChange={onChange} />)
|
||||
expect(screen.getAllByTestId('file-icon')).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('should call onChange with document on click', () => {
|
||||
render(<DocumentList list={mockList} onChange={onChange} />)
|
||||
fireEvent.click(screen.getByText('report'))
|
||||
expect(onChange).toHaveBeenCalledWith(mockList[0])
|
||||
})
|
||||
|
||||
it('should render empty list without errors', () => {
|
||||
const { container } = render(<DocumentList list={[]} onChange={onChange} />)
|
||||
expect(container.firstChild).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@ -3,7 +3,7 @@ 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'
|
||||
import DocumentPicker from '../index'
|
||||
|
||||
// Mock portal-to-follow-elem - always render content for testing
|
||||
vi.mock('@/app/components/base/portal-to-follow-elem', () => ({
|
||||
@ -52,25 +52,6 @@ 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)}`,
|
||||
@ -211,12 +192,6 @@ describe('DocumentPicker', () => {
|
||||
expect(screen.getByText('--')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render arrow icon', () => {
|
||||
renderComponent()
|
||||
|
||||
expect(screen.getByTestId('arrow-icon')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render general mode label', () => {
|
||||
renderComponent({
|
||||
value: {
|
||||
@ -473,7 +448,7 @@ describe('DocumentPicker', () => {
|
||||
describe('Memoization Logic', () => {
|
||||
it('should be wrapped with React.memo', () => {
|
||||
// React.memo components have a $$typeof property
|
||||
expect((DocumentPicker as any).$$typeof).toBeDefined()
|
||||
expect((DocumentPicker as unknown as { $$typeof: symbol }).$$typeof).toBeDefined()
|
||||
})
|
||||
|
||||
it('should compute parentModeLabel correctly with useMemo', () => {
|
||||
@ -952,7 +927,6 @@ describe('DocumentPicker', () => {
|
||||
|
||||
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
|
||||
@ -1026,8 +1000,9 @@ describe('DocumentPicker', () => {
|
||||
},
|
||||
})
|
||||
|
||||
// FileIcon should be rendered via DocumentFileIcon - pdf renders pdf icon
|
||||
expect(screen.getByTestId('file-pdf-icon')).toBeInTheDocument()
|
||||
// FileIcon should render an SVG icon for the file extension
|
||||
const trigger = screen.getByTestId('portal-trigger')
|
||||
expect(trigger.querySelector('svg')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,20 +1,7 @@
|
||||
import type { DocumentItem } from '@/models/datasets'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import PreviewDocumentPicker from './preview-document-picker'
|
||||
|
||||
// Override shared i18n mock for custom translations
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string, params?: Record<string, unknown>) => {
|
||||
if (key === 'preprocessDocument' && params?.num)
|
||||
return `${params.num} files`
|
||||
|
||||
const prefix = params?.ns ? `${params.ns}.` : ''
|
||||
return `${prefix}${key}`
|
||||
},
|
||||
}),
|
||||
}))
|
||||
import PreviewDocumentPicker from '../preview-document-picker'
|
||||
|
||||
// Mock portal-to-follow-elem - always render content for testing
|
||||
vi.mock('@/app/components/base/portal-to-follow-elem', () => ({
|
||||
@ -45,23 +32,6 @@ vi.mock('@/app/components/base/portal-to-follow-elem', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock icons
|
||||
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>,
|
||||
}))
|
||||
|
||||
// Factory function to create mock DocumentItem
|
||||
const createMockDocumentItem = (overrides: Partial<DocumentItem> = {}): DocumentItem => ({
|
||||
id: `doc-${Math.random().toString(36).substr(2, 9)}`,
|
||||
@ -134,19 +104,14 @@ describe('PreviewDocumentPicker', () => {
|
||||
expect(screen.getByText('--')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render arrow icon', () => {
|
||||
renderComponent()
|
||||
|
||||
expect(screen.getByTestId('arrow-icon')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render file icon', () => {
|
||||
renderComponent({
|
||||
value: createMockDocumentItem({ extension: 'txt' }),
|
||||
files: [], // Use empty files to avoid duplicate icons
|
||||
})
|
||||
|
||||
expect(screen.getByTestId('file-text-icon')).toBeInTheDocument()
|
||||
const trigger = screen.getByTestId('portal-trigger')
|
||||
expect(trigger.querySelector('svg')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render pdf icon for pdf extension', () => {
|
||||
@ -155,7 +120,8 @@ describe('PreviewDocumentPicker', () => {
|
||||
files: [], // Use empty files to avoid duplicate icons
|
||||
})
|
||||
|
||||
expect(screen.getByTestId('file-pdf-icon')).toBeInTheDocument()
|
||||
const trigger = screen.getByTestId('portal-trigger')
|
||||
expect(trigger.querySelector('svg')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -206,7 +172,8 @@ describe('PreviewDocumentPicker', () => {
|
||||
value: createMockDocumentItem({ name: 'test.docx', extension: 'docx' }),
|
||||
})
|
||||
|
||||
expect(screen.getByTestId('file-word-icon')).toBeInTheDocument()
|
||||
const trigger = screen.getByTestId('portal-trigger')
|
||||
expect(trigger.querySelector('svg')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -282,7 +249,7 @@ describe('PreviewDocumentPicker', () => {
|
||||
// Tests for component memoization
|
||||
describe('Component Memoization', () => {
|
||||
it('should be wrapped with React.memo', () => {
|
||||
expect((PreviewDocumentPicker as any).$$typeof).toBeDefined()
|
||||
expect((PreviewDocumentPicker as unknown as { $$typeof: symbol }).$$typeof).toBeDefined()
|
||||
})
|
||||
|
||||
it('should not re-render when props are the same', () => {
|
||||
@ -329,7 +296,6 @@ describe('PreviewDocumentPicker', () => {
|
||||
|
||||
renderComponent({ files, onChange })
|
||||
|
||||
// Click on a document
|
||||
fireEvent.click(screen.getByText('Document 2'))
|
||||
|
||||
// handleChange should call onChange with the selected item
|
||||
@ -506,21 +472,16 @@ describe('PreviewDocumentPicker', () => {
|
||||
})
|
||||
|
||||
describe('extension variations', () => {
|
||||
const extensions = [
|
||||
{ ext: 'txt', icon: 'file-text-icon' },
|
||||
{ ext: 'pdf', icon: 'file-pdf-icon' },
|
||||
{ ext: 'docx', icon: 'file-word-icon' },
|
||||
{ ext: 'xlsx', icon: 'file-excel-icon' },
|
||||
{ ext: 'md', icon: 'file-markdown-icon' },
|
||||
]
|
||||
const extensions = ['txt', 'pdf', 'docx', 'xlsx', 'md']
|
||||
|
||||
it.each(extensions)('should render correct icon for $ext extension', ({ ext, icon }) => {
|
||||
it.each(extensions)('should render icon for %s extension', (ext) => {
|
||||
renderComponent({
|
||||
value: createMockDocumentItem({ extension: ext }),
|
||||
files: [], // Use empty files to avoid duplicate icons
|
||||
})
|
||||
|
||||
expect(screen.getByTestId(icon)).toBeInTheDocument()
|
||||
const trigger = screen.getByTestId('portal-trigger')
|
||||
expect(trigger.querySelector('svg')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -543,7 +504,6 @@ describe('PreviewDocumentPicker', () => {
|
||||
|
||||
renderComponent({ files, onChange })
|
||||
|
||||
// Click on first document
|
||||
fireEvent.click(screen.getByText('Document 1'))
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith(files[0])
|
||||
@ -568,7 +528,7 @@ describe('PreviewDocumentPicker', () => {
|
||||
onChange={vi.fn()}
|
||||
/>,
|
||||
)
|
||||
expect(screen.getByText('3 files')).toBeInTheDocument()
|
||||
expect(screen.getByText(/dataset\.preprocessDocument/)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -609,7 +569,6 @@ describe('PreviewDocumentPicker', () => {
|
||||
|
||||
renderComponent({ files, onChange })
|
||||
|
||||
// Click first document
|
||||
fireEvent.click(screen.getByText('Document 1'))
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith(files[0])
|
||||
@ -624,11 +583,9 @@ describe('PreviewDocumentPicker', () => {
|
||||
|
||||
renderComponent({ files: customFiles, onChange })
|
||||
|
||||
// Click on first custom file
|
||||
fireEvent.click(screen.getByText('Custom File 1'))
|
||||
expect(onChange).toHaveBeenCalledWith(customFiles[0])
|
||||
|
||||
// Click on second custom file
|
||||
fireEvent.click(screen.getByText('Custom File 2'))
|
||||
expect(onChange).toHaveBeenCalledWith(customFiles[1])
|
||||
})
|
||||
Reference in New Issue
Block a user