mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 18:08:07 +08:00
test: add tests for dataset document detail (#31274)
Co-authored-by: CodingOnStar <hanxujiang@dify.ai> Co-authored-by: CodingOnStar <hanxujiang@dify.com>
This commit is contained in:
@ -0,0 +1,279 @@
|
||||
import type { Datasource } from '@/app/components/rag-pipeline/components/panel/test-run/types'
|
||||
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
|
||||
import type { NotionPage } from '@/models/common'
|
||||
import type { CrawlResultItem, CustomFile, FileIndexingEstimateResponse, FileItem } from '@/models/datasets'
|
||||
import type { OnlineDriveFile } from '@/models/pipeline'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { DatasourceType } from '@/models/pipeline'
|
||||
import { StepOnePreview, StepTwoPreview } from './preview-panel'
|
||||
|
||||
// Mock context hooks (底层依赖)
|
||||
vi.mock('@/context/dataset-detail', () => ({
|
||||
useDatasetDetailContextWithSelector: vi.fn((selector: (state: unknown) => unknown) => {
|
||||
const mockState = {
|
||||
dataset: {
|
||||
id: 'mock-dataset-id',
|
||||
doc_form: 'text_model',
|
||||
pipeline_id: 'mock-pipeline-id',
|
||||
},
|
||||
}
|
||||
return selector(mockState)
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock API hooks (底层依赖)
|
||||
vi.mock('@/service/use-common', () => ({
|
||||
useFilePreview: vi.fn(() => ({
|
||||
data: { content: 'Mock file content for testing' },
|
||||
isFetching: false,
|
||||
})),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-pipeline', () => ({
|
||||
usePreviewOnlineDocument: vi.fn(() => ({
|
||||
mutateAsync: vi.fn().mockResolvedValue({ content: 'Mock document content' }),
|
||||
isPending: false,
|
||||
})),
|
||||
}))
|
||||
|
||||
// Mock data source store
|
||||
vi.mock('../data-source/store', () => ({
|
||||
useDataSourceStore: vi.fn(() => ({
|
||||
getState: () => ({ currentCredentialId: 'mock-credential-id' }),
|
||||
})),
|
||||
}))
|
||||
|
||||
describe('StepOnePreview', () => {
|
||||
const mockDatasource: Datasource = {
|
||||
nodeId: 'test-node-id',
|
||||
nodeData: { type: 'data-source' } as unknown as DataSourceNodeType,
|
||||
}
|
||||
|
||||
const mockLocalFile: CustomFile = {
|
||||
id: 'file-1',
|
||||
name: 'test-file.txt',
|
||||
type: 'text/plain',
|
||||
size: 1024,
|
||||
progress: 100,
|
||||
extension: 'txt',
|
||||
} as unknown as CustomFile
|
||||
|
||||
const mockWebsite: CrawlResultItem = {
|
||||
source_url: 'https://example.com',
|
||||
title: 'Example Site',
|
||||
markdown: 'Mock markdown content',
|
||||
} as CrawlResultItem
|
||||
|
||||
const defaultProps = {
|
||||
datasource: mockDatasource,
|
||||
currentLocalFile: undefined,
|
||||
currentDocument: undefined,
|
||||
currentWebsite: undefined,
|
||||
hidePreviewLocalFile: vi.fn(),
|
||||
hidePreviewOnlineDocument: vi.fn(),
|
||||
hideWebsitePreview: vi.fn(),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
const { container } = render(<StepOnePreview {...defaultProps} />)
|
||||
expect(container.querySelector('.h-full')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render container with correct structure', () => {
|
||||
const { container } = render(<StepOnePreview {...defaultProps} />)
|
||||
expect(container.querySelector('.flex.h-full.flex-col')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Conditional Rendering - FilePreview', () => {
|
||||
it('should render FilePreview when currentLocalFile is provided', () => {
|
||||
render(<StepOnePreview {...defaultProps} currentLocalFile={mockLocalFile} />)
|
||||
// FilePreview renders a preview header with file name
|
||||
expect(screen.getByText(/test-file/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render FilePreview when currentLocalFile is undefined', () => {
|
||||
const { container } = render(<StepOnePreview {...defaultProps} currentLocalFile={undefined} />)
|
||||
// Container should still render but without file preview content
|
||||
expect(container.querySelector('.h-full')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Conditional Rendering - WebsitePreview', () => {
|
||||
it('should render WebsitePreview when currentWebsite is provided', () => {
|
||||
render(<StepOnePreview {...defaultProps} currentWebsite={mockWebsite} />)
|
||||
// WebsitePreview displays the website title and URL
|
||||
expect(screen.getByText('Example Site')).toBeInTheDocument()
|
||||
expect(screen.getByText('https://example.com')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render WebsitePreview when currentWebsite is undefined', () => {
|
||||
const { container } = render(<StepOnePreview {...defaultProps} currentWebsite={undefined} />)
|
||||
expect(container.querySelector('.h-full')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call hideWebsitePreview when close button is clicked', () => {
|
||||
const hideWebsitePreview = vi.fn()
|
||||
render(
|
||||
<StepOnePreview
|
||||
{...defaultProps}
|
||||
currentWebsite={mockWebsite}
|
||||
hideWebsitePreview={hideWebsitePreview}
|
||||
/>,
|
||||
)
|
||||
|
||||
// Find and click the close button (RiCloseLine icon)
|
||||
const closeButton = screen.getByRole('button')
|
||||
closeButton.click()
|
||||
|
||||
expect(hideWebsitePreview).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle website with long markdown content', () => {
|
||||
const longWebsite: CrawlResultItem = {
|
||||
...mockWebsite,
|
||||
markdown: 'A'.repeat(10000),
|
||||
}
|
||||
render(<StepOnePreview {...defaultProps} currentWebsite={longWebsite} />)
|
||||
expect(screen.getByText('Example Site')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('StepTwoPreview', () => {
|
||||
const mockFileList: FileItem[] = [
|
||||
{
|
||||
file: {
|
||||
id: 'file-1',
|
||||
name: 'file1.txt',
|
||||
extension: 'txt',
|
||||
size: 1024,
|
||||
} as CustomFile,
|
||||
progress: 100,
|
||||
},
|
||||
{
|
||||
file: {
|
||||
id: 'file-2',
|
||||
name: 'file2.txt',
|
||||
extension: 'txt',
|
||||
size: 2048,
|
||||
} as CustomFile,
|
||||
progress: 100,
|
||||
},
|
||||
] as FileItem[]
|
||||
|
||||
const mockOnlineDocuments: (NotionPage & { workspace_id: string })[] = [
|
||||
{
|
||||
page_id: 'page-1',
|
||||
page_name: 'Page 1',
|
||||
type: 'page',
|
||||
workspace_id: 'workspace-1',
|
||||
page_icon: null,
|
||||
is_bound: false,
|
||||
parent_id: '',
|
||||
},
|
||||
]
|
||||
|
||||
const mockWebsitePages: CrawlResultItem[] = [
|
||||
{ source_url: 'https://example.com', title: 'Example', markdown: 'Content' } as CrawlResultItem,
|
||||
]
|
||||
|
||||
const mockOnlineDriveFiles: OnlineDriveFile[] = [
|
||||
{ id: 'drive-1', name: 'drive-file.txt' } as OnlineDriveFile,
|
||||
]
|
||||
|
||||
const mockEstimateData: FileIndexingEstimateResponse = {
|
||||
tokens: 1000,
|
||||
total_price: 0.01,
|
||||
total_segments: 10,
|
||||
} as FileIndexingEstimateResponse
|
||||
|
||||
const defaultProps = {
|
||||
datasourceType: DatasourceType.localFile,
|
||||
localFileList: mockFileList,
|
||||
onlineDocuments: mockOnlineDocuments,
|
||||
websitePages: mockWebsitePages,
|
||||
selectedOnlineDriveFileList: mockOnlineDriveFiles,
|
||||
isIdle: true,
|
||||
isPendingPreview: false,
|
||||
estimateData: mockEstimateData,
|
||||
onPreview: vi.fn(),
|
||||
handlePreviewFileChange: vi.fn(),
|
||||
handlePreviewOnlineDocumentChange: vi.fn(),
|
||||
handlePreviewWebsitePageChange: vi.fn(),
|
||||
handlePreviewOnlineDriveFileChange: vi.fn(),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
const { container } = render(<StepTwoPreview {...defaultProps} />)
|
||||
expect(container.querySelector('.h-full')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render ChunkPreview component structure', () => {
|
||||
const { container } = render(<StepTwoPreview {...defaultProps} />)
|
||||
expect(container.querySelector('.flex.h-full.flex-col')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Props Passing', () => {
|
||||
it('should render preview button when isIdle is true', () => {
|
||||
render(<StepTwoPreview {...defaultProps} isIdle={true} />)
|
||||
// ChunkPreview shows a preview button when idle
|
||||
const previewButton = screen.queryByRole('button')
|
||||
expect(previewButton).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call onPreview when preview button is clicked', () => {
|
||||
const onPreview = vi.fn()
|
||||
render(<StepTwoPreview {...defaultProps} isIdle={true} onPreview={onPreview} />)
|
||||
|
||||
// Find and click the preview button
|
||||
const buttons = screen.getAllByRole('button')
|
||||
const previewButton = buttons.find(btn => btn.textContent?.toLowerCase().includes('preview'))
|
||||
if (previewButton) {
|
||||
previewButton.click()
|
||||
expect(onPreview).toHaveBeenCalled()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle empty localFileList', () => {
|
||||
const { container } = render(<StepTwoPreview {...defaultProps} localFileList={[]} />)
|
||||
expect(container.querySelector('.h-full')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle empty onlineDocuments', () => {
|
||||
const { container } = render(<StepTwoPreview {...defaultProps} onlineDocuments={[]} />)
|
||||
expect(container.querySelector('.h-full')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle empty websitePages', () => {
|
||||
const { container } = render(<StepTwoPreview {...defaultProps} websitePages={[]} />)
|
||||
expect(container.querySelector('.h-full')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle empty onlineDriveFiles', () => {
|
||||
const { container } = render(<StepTwoPreview {...defaultProps} selectedOnlineDriveFileList={[]} />)
|
||||
expect(container.querySelector('.h-full')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle undefined estimateData', () => {
|
||||
const { container } = render(<StepTwoPreview {...defaultProps} estimateData={undefined} />)
|
||||
expect(container.querySelector('.h-full')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,413 @@
|
||||
import type { Datasource } from '@/app/components/rag-pipeline/components/panel/test-run/types'
|
||||
import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-source/types'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { DatasourceType } from '@/models/pipeline'
|
||||
import StepOneContent from './step-one-content'
|
||||
|
||||
// Mock context providers and hooks (底层依赖)
|
||||
vi.mock('@/context/modal-context', () => ({
|
||||
useModalContext: vi.fn(() => ({
|
||||
setShowPricingModal: vi.fn(),
|
||||
})),
|
||||
}))
|
||||
|
||||
// Mock billing components that have complex provider dependencies
|
||||
vi.mock('@/app/components/billing/vector-space-full', () => ({
|
||||
default: () => <div data-testid="vector-space-full">Vector Space Full</div>,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/billing/upgrade-btn', () => ({
|
||||
default: ({ onClick }: { onClick?: () => void }) => (
|
||||
<button data-testid="upgrade-btn" onClick={onClick}>Upgrade</button>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock data source store
|
||||
vi.mock('../data-source/store', () => ({
|
||||
useDataSourceStore: vi.fn(() => ({
|
||||
getState: () => ({
|
||||
localFileList: [],
|
||||
currentCredentialId: 'mock-credential-id',
|
||||
}),
|
||||
setState: vi.fn(),
|
||||
})),
|
||||
useDataSourceStoreWithSelector: vi.fn((selector: (state: unknown) => unknown) => {
|
||||
const mockState = {
|
||||
localFileList: [],
|
||||
onlineDocuments: [],
|
||||
websitePages: [],
|
||||
selectedOnlineDriveFileList: [],
|
||||
}
|
||||
return selector(mockState)
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock file upload config
|
||||
vi.mock('@/service/use-common', () => ({
|
||||
useFileUploadConfig: vi.fn(() => ({
|
||||
data: {
|
||||
file_size_limit: 15 * 1024 * 1024,
|
||||
batch_count_limit: 20,
|
||||
document_file_extensions: ['.txt', '.md', '.pdf'],
|
||||
},
|
||||
isLoading: false,
|
||||
})),
|
||||
}))
|
||||
|
||||
// Mock hooks used by data source options
|
||||
vi.mock('../hooks', () => ({
|
||||
useDatasourceOptions: vi.fn(() => [
|
||||
{ label: 'Local File', value: 'node-1', data: { type: 'data-source' } },
|
||||
]),
|
||||
}))
|
||||
|
||||
// Mock useDatasourceIcon hook to avoid complex data source list transformation
|
||||
vi.mock('../data-source-options/hooks', () => ({
|
||||
useDatasourceIcon: vi.fn(() => '/icons/local-file.svg'),
|
||||
}))
|
||||
|
||||
// Mock the entire local-file component since it has deep context dependencies
|
||||
vi.mock('../data-source/local-file', () => ({
|
||||
default: ({ allowedExtensions, supportBatchUpload }: {
|
||||
allowedExtensions: string[]
|
||||
supportBatchUpload: boolean
|
||||
}) => (
|
||||
<div data-testid="local-file">
|
||||
<div>Drag and drop file here</div>
|
||||
<span data-testid="allowed-extensions">{allowedExtensions.join(',')}</span>
|
||||
<span data-testid="support-batch-upload">{String(supportBatchUpload)}</span>
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock online documents since it has complex OAuth/API dependencies
|
||||
vi.mock('../data-source/online-documents', () => ({
|
||||
default: ({ nodeId, onCredentialChange }: {
|
||||
nodeId: string
|
||||
onCredentialChange: (credentialId: string) => void
|
||||
}) => (
|
||||
<div data-testid="online-documents">
|
||||
<span data-testid="online-doc-node-id">{nodeId}</span>
|
||||
<button data-testid="credential-change-btn" onClick={() => onCredentialChange('new-credential')}>
|
||||
Change Credential
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock website crawl
|
||||
vi.mock('../data-source/website-crawl', () => ({
|
||||
default: ({ nodeId, onCredentialChange }: {
|
||||
nodeId: string
|
||||
onCredentialChange: (credentialId: string) => void
|
||||
}) => (
|
||||
<div data-testid="website-crawl">
|
||||
<span data-testid="website-crawl-node-id">{nodeId}</span>
|
||||
<button data-testid="website-credential-btn" onClick={() => onCredentialChange('website-credential')}>
|
||||
Change Website Credential
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock online drive
|
||||
vi.mock('../data-source/online-drive', () => ({
|
||||
default: ({ nodeId, onCredentialChange }: {
|
||||
nodeId: string
|
||||
onCredentialChange: (credentialId: string) => void
|
||||
}) => (
|
||||
<div data-testid="online-drive">
|
||||
<span data-testid="online-drive-node-id">{nodeId}</span>
|
||||
<button data-testid="drive-credential-btn" onClick={() => onCredentialChange('drive-credential')}>
|
||||
Change Drive Credential
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock locale context
|
||||
vi.mock('@/context/i18n', () => ({
|
||||
useLocale: vi.fn(() => 'en'),
|
||||
useDocLink: () => (path: string) => `https://docs.dify.ai${path}`,
|
||||
}))
|
||||
|
||||
// Mock theme hook
|
||||
vi.mock('@/hooks/use-theme', () => ({
|
||||
default: vi.fn(() => 'light'),
|
||||
}))
|
||||
|
||||
// Mock upload service
|
||||
vi.mock('@/service/base', () => ({
|
||||
upload: vi.fn().mockResolvedValue({ id: 'uploaded-file-id' }),
|
||||
}))
|
||||
|
||||
// Mock next/navigation
|
||||
vi.mock('next/navigation', () => ({
|
||||
useParams: () => ({ datasetId: 'mock-dataset-id' }),
|
||||
useRouter: () => ({ push: vi.fn() }),
|
||||
usePathname: () => '/datasets/mock-dataset-id',
|
||||
}))
|
||||
|
||||
// Mock pipeline service hooks
|
||||
vi.mock('@/service/use-pipeline', () => ({
|
||||
useNotionWorkspaces: vi.fn(() => ({
|
||||
data: [],
|
||||
isLoading: false,
|
||||
})),
|
||||
useNotionPages: vi.fn(() => ({
|
||||
data: { pages: [] },
|
||||
isLoading: false,
|
||||
})),
|
||||
useDataSourceList: vi.fn(() => ({
|
||||
data: [
|
||||
{
|
||||
type: 'local_file',
|
||||
declaration: {
|
||||
identity: {
|
||||
name: 'Local File',
|
||||
icon: '/icons/local-file.svg',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
isSuccess: true,
|
||||
isLoading: false,
|
||||
})),
|
||||
useCrawlResult: vi.fn(() => ({
|
||||
data: { data: [] },
|
||||
isLoading: false,
|
||||
})),
|
||||
useSupportedOauth: vi.fn(() => ({
|
||||
data: [],
|
||||
isLoading: false,
|
||||
})),
|
||||
useOnlineDriveCredentialList: vi.fn(() => ({
|
||||
data: [],
|
||||
isLoading: false,
|
||||
})),
|
||||
useOnlineDriveFileList: vi.fn(() => ({
|
||||
data: { data: [] },
|
||||
isLoading: false,
|
||||
})),
|
||||
}))
|
||||
|
||||
describe('StepOneContent', () => {
|
||||
const mockDatasource: Datasource = {
|
||||
nodeId: 'test-node-id',
|
||||
nodeData: {
|
||||
type: 'data-source',
|
||||
fileExtensions: ['txt', 'pdf'],
|
||||
title: 'Test Data Source',
|
||||
desc: 'Test description',
|
||||
} as unknown as DataSourceNodeType,
|
||||
}
|
||||
|
||||
const mockPipelineNodes: Node<DataSourceNodeType>[] = [
|
||||
{
|
||||
id: 'node-1',
|
||||
data: {
|
||||
type: 'data-source',
|
||||
title: 'Node 1',
|
||||
desc: 'Description 1',
|
||||
} as unknown as DataSourceNodeType,
|
||||
} as Node<DataSourceNodeType>,
|
||||
{
|
||||
id: 'node-2',
|
||||
data: {
|
||||
type: 'data-source',
|
||||
title: 'Node 2',
|
||||
desc: 'Description 2',
|
||||
} as unknown as DataSourceNodeType,
|
||||
} as Node<DataSourceNodeType>,
|
||||
]
|
||||
|
||||
const defaultProps = {
|
||||
datasource: mockDatasource,
|
||||
datasourceType: DatasourceType.localFile,
|
||||
pipelineNodes: mockPipelineNodes,
|
||||
supportBatchUpload: true,
|
||||
localFileListLength: 0,
|
||||
isShowVectorSpaceFull: false,
|
||||
showSelect: false,
|
||||
totalOptions: 10,
|
||||
selectedOptions: 5,
|
||||
tip: 'Test tip',
|
||||
nextBtnDisabled: false,
|
||||
onSelectDataSource: vi.fn(),
|
||||
onCredentialChange: vi.fn(),
|
||||
onSelectAll: vi.fn(),
|
||||
onNextStep: vi.fn(),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
const { container } = render(<StepOneContent {...defaultProps} />)
|
||||
expect(container.querySelector('.flex.flex-col')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render DataSourceOptions component', () => {
|
||||
render(<StepOneContent {...defaultProps} />)
|
||||
// DataSourceOptions renders option cards
|
||||
expect(screen.getByText('Local File')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render Actions component with next button', () => {
|
||||
render(<StepOneContent {...defaultProps} />)
|
||||
// Actions component renders a next step button (uses i18n key)
|
||||
const nextButton = screen.getByRole('button', { name: /datasetCreation\.stepOne\.button/i })
|
||||
expect(nextButton).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Conditional Rendering - DatasourceType', () => {
|
||||
it('should render LocalFile component when datasourceType is localFile', () => {
|
||||
render(<StepOneContent {...defaultProps} datasourceType={DatasourceType.localFile} />)
|
||||
expect(screen.getByTestId('local-file')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render OnlineDocuments component when datasourceType is onlineDocument', () => {
|
||||
render(<StepOneContent {...defaultProps} datasourceType={DatasourceType.onlineDocument} />)
|
||||
expect(screen.getByTestId('online-documents')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render WebsiteCrawl component when datasourceType is websiteCrawl', () => {
|
||||
render(<StepOneContent {...defaultProps} datasourceType={DatasourceType.websiteCrawl} />)
|
||||
expect(screen.getByTestId('website-crawl')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render OnlineDrive component when datasourceType is onlineDrive', () => {
|
||||
render(<StepOneContent {...defaultProps} datasourceType={DatasourceType.onlineDrive} />)
|
||||
expect(screen.getByTestId('online-drive')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render data source component when datasourceType is undefined', () => {
|
||||
const { container } = render(<StepOneContent {...defaultProps} datasourceType={undefined} />)
|
||||
expect(container.querySelector('.flex.flex-col')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('local-file')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Conditional Rendering - VectorSpaceFull', () => {
|
||||
it('should render VectorSpaceFull when isShowVectorSpaceFull is true', () => {
|
||||
render(<StepOneContent {...defaultProps} isShowVectorSpaceFull={true} />)
|
||||
expect(screen.getByTestId('vector-space-full')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render VectorSpaceFull when isShowVectorSpaceFull is false', () => {
|
||||
render(<StepOneContent {...defaultProps} isShowVectorSpaceFull={false} />)
|
||||
expect(screen.queryByTestId('vector-space-full')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Conditional Rendering - UpgradeCard', () => {
|
||||
it('should render UpgradeCard when batch upload not supported and has local files', () => {
|
||||
render(
|
||||
<StepOneContent
|
||||
{...defaultProps}
|
||||
supportBatchUpload={false}
|
||||
datasourceType={DatasourceType.localFile}
|
||||
localFileListLength={3}
|
||||
/>,
|
||||
)
|
||||
// UpgradeCard contains an upgrade button
|
||||
expect(screen.getByTestId('upgrade-btn')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render UpgradeCard when batch upload is supported', () => {
|
||||
render(
|
||||
<StepOneContent
|
||||
{...defaultProps}
|
||||
supportBatchUpload={true}
|
||||
datasourceType={DatasourceType.localFile}
|
||||
localFileListLength={3}
|
||||
/>,
|
||||
)
|
||||
// The upgrade card should not be present
|
||||
const upgradeCard = screen.queryByText(/upload multiple files/i)
|
||||
expect(upgradeCard).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render UpgradeCard when datasourceType is not localFile', () => {
|
||||
render(
|
||||
<StepOneContent
|
||||
{...defaultProps}
|
||||
supportBatchUpload={false}
|
||||
datasourceType={undefined}
|
||||
localFileListLength={3}
|
||||
/>,
|
||||
)
|
||||
expect(screen.queryByTestId('upgrade-btn')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render UpgradeCard when localFileListLength is 0', () => {
|
||||
render(
|
||||
<StepOneContent
|
||||
{...defaultProps}
|
||||
supportBatchUpload={false}
|
||||
datasourceType={DatasourceType.localFile}
|
||||
localFileListLength={0}
|
||||
/>,
|
||||
)
|
||||
expect(screen.queryByTestId('upgrade-btn')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('User Interactions', () => {
|
||||
it('should call onNextStep when next button is clicked', () => {
|
||||
const onNextStep = vi.fn()
|
||||
render(<StepOneContent {...defaultProps} onNextStep={onNextStep} />)
|
||||
|
||||
const nextButton = screen.getByRole('button', { name: /datasetCreation\.stepOne\.button/i })
|
||||
nextButton.click()
|
||||
|
||||
expect(onNextStep).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should disable next button when nextBtnDisabled is true', () => {
|
||||
render(<StepOneContent {...defaultProps} nextBtnDisabled={true} />)
|
||||
|
||||
const nextButton = screen.getByRole('button', { name: /datasetCreation\.stepOne\.button/i })
|
||||
expect(nextButton).toBeDisabled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle undefined datasource when datasourceType is undefined', () => {
|
||||
const { container } = render(
|
||||
<StepOneContent {...defaultProps} datasource={undefined} datasourceType={undefined} />,
|
||||
)
|
||||
expect(container.querySelector('.flex.flex-col')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle empty pipelineNodes array', () => {
|
||||
render(<StepOneContent {...defaultProps} pipelineNodes={[]} />)
|
||||
// Should still render but DataSourceOptions may show no options
|
||||
const { container } = render(<StepOneContent {...defaultProps} pipelineNodes={[]} />)
|
||||
expect(container.querySelector('.flex.flex-col')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle undefined totalOptions', () => {
|
||||
render(<StepOneContent {...defaultProps} totalOptions={undefined} />)
|
||||
const nextButton = screen.getByRole('button', { name: /datasetCreation\.stepOne\.button/i })
|
||||
expect(nextButton).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle undefined selectedOptions', () => {
|
||||
render(<StepOneContent {...defaultProps} selectedOptions={undefined} />)
|
||||
const nextButton = screen.getByRole('button', { name: /datasetCreation\.stepOne\.button/i })
|
||||
expect(nextButton).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle empty tip', () => {
|
||||
render(<StepOneContent {...defaultProps} tip="" />)
|
||||
const nextButton = screen.getByRole('button', { name: /datasetCreation\.stepOne\.button/i })
|
||||
expect(nextButton).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,97 @@
|
||||
import type { InitialDocumentDetail } from '@/models/pipeline'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import StepThreeContent from './step-three-content'
|
||||
|
||||
// Mock context hooks used by Processing component
|
||||
vi.mock('@/context/dataset-detail', () => ({
|
||||
useDatasetDetailContextWithSelector: vi.fn((selector: (state: unknown) => unknown) => {
|
||||
const mockState = {
|
||||
dataset: {
|
||||
id: 'mock-dataset-id',
|
||||
indexing_technique: 'high_quality',
|
||||
retrieval_model_dict: {
|
||||
search_method: 'semantic_search',
|
||||
},
|
||||
},
|
||||
}
|
||||
return selector(mockState)
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/i18n', () => ({
|
||||
useDocLink: () => (path: string) => `https://docs.dify.ai${path}`,
|
||||
}))
|
||||
|
||||
// Mock EmbeddingProcess component as it has complex dependencies
|
||||
vi.mock('../processing/embedding-process', () => ({
|
||||
default: ({ datasetId, batchId, documents }: {
|
||||
datasetId: string
|
||||
batchId: string
|
||||
documents: InitialDocumentDetail[]
|
||||
}) => (
|
||||
<div data-testid="embedding-process">
|
||||
<span data-testid="dataset-id">{datasetId}</span>
|
||||
<span data-testid="batch-id">{batchId}</span>
|
||||
<span data-testid="documents-count">{documents.length}</span>
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
describe('StepThreeContent', () => {
|
||||
const mockDocuments: InitialDocumentDetail[] = [
|
||||
{ id: 'doc1', name: 'Document 1' } as InitialDocumentDetail,
|
||||
{ id: 'doc2', name: 'Document 2' } as InitialDocumentDetail,
|
||||
]
|
||||
|
||||
const defaultProps = {
|
||||
batchId: 'test-batch-id',
|
||||
documents: mockDocuments,
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
render(<StepThreeContent {...defaultProps} />)
|
||||
expect(screen.getByTestId('embedding-process')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render Processing component', () => {
|
||||
render(<StepThreeContent {...defaultProps} />)
|
||||
expect(screen.getByTestId('embedding-process')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Props', () => {
|
||||
it('should pass batchId to Processing component', () => {
|
||||
render(<StepThreeContent {...defaultProps} />)
|
||||
expect(screen.getByTestId('batch-id')).toHaveTextContent('test-batch-id')
|
||||
})
|
||||
|
||||
it('should pass documents to Processing component', () => {
|
||||
render(<StepThreeContent {...defaultProps} />)
|
||||
expect(screen.getByTestId('documents-count')).toHaveTextContent('2')
|
||||
})
|
||||
|
||||
it('should handle empty documents array', () => {
|
||||
render(<StepThreeContent batchId="test-batch-id" documents={[]} />)
|
||||
expect(screen.getByTestId('documents-count')).toHaveTextContent('0')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should render with different batchId', () => {
|
||||
render(<StepThreeContent batchId="another-batch-id" documents={mockDocuments} />)
|
||||
expect(screen.getByTestId('batch-id')).toHaveTextContent('another-batch-id')
|
||||
})
|
||||
|
||||
it('should render with single document', () => {
|
||||
const singleDocument = [mockDocuments[0]]
|
||||
render(<StepThreeContent batchId="test-batch-id" documents={singleDocument} />)
|
||||
expect(screen.getByTestId('documents-count')).toHaveTextContent('1')
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -0,0 +1,136 @@
|
||||
import type { RefObject } from 'react'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import StepTwoContent from './step-two-content'
|
||||
|
||||
// Mock ProcessDocuments component as it has complex hook dependencies
|
||||
vi.mock('../process-documents', () => ({
|
||||
default: vi.fn().mockImplementation(({
|
||||
dataSourceNodeId,
|
||||
isRunning,
|
||||
onProcess,
|
||||
onPreview,
|
||||
onSubmit,
|
||||
onBack,
|
||||
}: {
|
||||
dataSourceNodeId: string
|
||||
isRunning: boolean
|
||||
onProcess: () => void
|
||||
onPreview: () => void
|
||||
onSubmit: (data: Record<string, unknown>) => void
|
||||
onBack: () => void
|
||||
}) => (
|
||||
<div data-testid="process-documents">
|
||||
<span data-testid="data-source-node-id">{dataSourceNodeId}</span>
|
||||
<span data-testid="is-running">{String(isRunning)}</span>
|
||||
<button data-testid="process-btn" onClick={onProcess}>Process</button>
|
||||
<button data-testid="preview-btn" onClick={onPreview}>Preview</button>
|
||||
<button data-testid="submit-btn" onClick={() => onSubmit({ key: 'value' })}>Submit</button>
|
||||
<button data-testid="back-btn" onClick={onBack}>Back</button>
|
||||
</div>
|
||||
)),
|
||||
}))
|
||||
|
||||
describe('StepTwoContent', () => {
|
||||
const mockFormRef: RefObject<{ submit: () => void } | null> = { current: null }
|
||||
|
||||
const defaultProps = {
|
||||
formRef: mockFormRef,
|
||||
dataSourceNodeId: 'test-node-id',
|
||||
isRunning: false,
|
||||
onProcess: vi.fn(),
|
||||
onPreview: vi.fn(),
|
||||
onSubmit: vi.fn(),
|
||||
onBack: vi.fn(),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
render(<StepTwoContent {...defaultProps} />)
|
||||
expect(screen.getByTestId('process-documents')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render ProcessDocuments component', () => {
|
||||
render(<StepTwoContent {...defaultProps} />)
|
||||
expect(screen.getByTestId('process-documents')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Props', () => {
|
||||
it('should pass dataSourceNodeId to ProcessDocuments', () => {
|
||||
render(<StepTwoContent {...defaultProps} />)
|
||||
expect(screen.getByTestId('data-source-node-id')).toHaveTextContent('test-node-id')
|
||||
})
|
||||
|
||||
it('should pass isRunning false to ProcessDocuments', () => {
|
||||
render(<StepTwoContent {...defaultProps} isRunning={false} />)
|
||||
expect(screen.getByTestId('is-running')).toHaveTextContent('false')
|
||||
})
|
||||
|
||||
it('should pass isRunning true to ProcessDocuments', () => {
|
||||
render(<StepTwoContent {...defaultProps} isRunning={true} />)
|
||||
expect(screen.getByTestId('is-running')).toHaveTextContent('true')
|
||||
})
|
||||
|
||||
it('should pass different dataSourceNodeId', () => {
|
||||
render(<StepTwoContent {...defaultProps} dataSourceNodeId="different-node-id" />)
|
||||
expect(screen.getByTestId('data-source-node-id')).toHaveTextContent('different-node-id')
|
||||
})
|
||||
})
|
||||
|
||||
describe('User Interactions', () => {
|
||||
it('should call onProcess when process button is clicked', () => {
|
||||
const onProcess = vi.fn()
|
||||
render(<StepTwoContent {...defaultProps} onProcess={onProcess} />)
|
||||
|
||||
screen.getByTestId('process-btn').click()
|
||||
|
||||
expect(onProcess).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should call onPreview when preview button is clicked', () => {
|
||||
const onPreview = vi.fn()
|
||||
render(<StepTwoContent {...defaultProps} onPreview={onPreview} />)
|
||||
|
||||
screen.getByTestId('preview-btn').click()
|
||||
|
||||
expect(onPreview).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should call onSubmit when submit button is clicked', () => {
|
||||
const onSubmit = vi.fn()
|
||||
render(<StepTwoContent {...defaultProps} onSubmit={onSubmit} />)
|
||||
|
||||
screen.getByTestId('submit-btn').click()
|
||||
|
||||
expect(onSubmit).toHaveBeenCalledTimes(1)
|
||||
expect(onSubmit).toHaveBeenCalledWith({ key: 'value' })
|
||||
})
|
||||
|
||||
it('should call onBack when back button is clicked', () => {
|
||||
const onBack = vi.fn()
|
||||
render(<StepTwoContent {...defaultProps} onBack={onBack} />)
|
||||
|
||||
screen.getByTestId('back-btn').click()
|
||||
|
||||
expect(onBack).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle empty dataSourceNodeId', () => {
|
||||
render(<StepTwoContent {...defaultProps} dataSourceNodeId="" />)
|
||||
expect(screen.getByTestId('data-source-node-id')).toHaveTextContent('')
|
||||
})
|
||||
|
||||
it('should handle null formRef', () => {
|
||||
const nullRef = { current: null }
|
||||
render(<StepTwoContent {...defaultProps} formRef={nullRef} />)
|
||||
expect(screen.getByTestId('process-documents')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user