mirror of
https://github.com/langgenius/dify.git
synced 2026-03-16 20:37:42 +08:00
Co-authored-by: CodingOnStar <hanxujiang@dify.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
363 lines
13 KiB
TypeScript
363 lines
13 KiB
TypeScript
import type { DataSet } from '@/models/datasets'
|
|
import type { RetrievalConfig } from '@/types/app'
|
|
import { render, screen } from '@testing-library/react'
|
|
import { ChunkingMode, DatasetPermission, DataSourceType } from '@/models/datasets'
|
|
import { RETRIEVE_METHOD } from '@/types/app'
|
|
import { IndexingType } from '../../../create/step-two'
|
|
import ExternalKnowledgeSection from './external-knowledge-section'
|
|
|
|
describe('ExternalKnowledgeSection', () => {
|
|
const mockRetrievalConfig: RetrievalConfig = {
|
|
search_method: RETRIEVE_METHOD.semantic,
|
|
reranking_enable: false,
|
|
reranking_model: {
|
|
reranking_provider_name: '',
|
|
reranking_model_name: '',
|
|
},
|
|
top_k: 3,
|
|
score_threshold_enabled: false,
|
|
score_threshold: 0.5,
|
|
}
|
|
|
|
const mockDataset: DataSet = {
|
|
id: 'dataset-1',
|
|
name: 'External Dataset',
|
|
description: 'External dataset description',
|
|
permission: DatasetPermission.onlyMe,
|
|
icon_info: {
|
|
icon_type: 'emoji',
|
|
icon: '📚',
|
|
icon_background: '#FFFFFF',
|
|
icon_url: '',
|
|
},
|
|
indexing_technique: IndexingType.QUALIFIED,
|
|
indexing_status: 'completed',
|
|
data_source_type: DataSourceType.FILE,
|
|
doc_form: ChunkingMode.text,
|
|
embedding_model: 'text-embedding-ada-002',
|
|
embedding_model_provider: 'openai',
|
|
embedding_available: true,
|
|
app_count: 0,
|
|
document_count: 5,
|
|
total_document_count: 5,
|
|
word_count: 1000,
|
|
provider: 'external',
|
|
tags: [],
|
|
partial_member_list: [],
|
|
external_knowledge_info: {
|
|
external_knowledge_id: 'ext-knowledge-123',
|
|
external_knowledge_api_id: 'api-456',
|
|
external_knowledge_api_name: 'My External API',
|
|
external_knowledge_api_endpoint: 'https://api.external.example.com/v1',
|
|
},
|
|
external_retrieval_model: {
|
|
top_k: 5,
|
|
score_threshold: 0.8,
|
|
score_threshold_enabled: true,
|
|
},
|
|
retrieval_model_dict: mockRetrievalConfig,
|
|
retrieval_model: mockRetrievalConfig,
|
|
built_in_field_enabled: false,
|
|
keyword_number: 10,
|
|
created_by: 'user-1',
|
|
updated_by: 'user-1',
|
|
updated_at: Date.now(),
|
|
runtime_mode: 'general',
|
|
enable_api: true,
|
|
is_multimodal: false,
|
|
}
|
|
|
|
const defaultProps = {
|
|
currentDataset: mockDataset,
|
|
topK: 5,
|
|
scoreThreshold: 0.8,
|
|
scoreThresholdEnabled: true,
|
|
handleSettingsChange: vi.fn(),
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
describe('Rendering', () => {
|
|
it('should render without crashing', () => {
|
|
render(<ExternalKnowledgeSection {...defaultProps} />)
|
|
expect(screen.getByText(/form\.retrievalSetting\.title/i)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render retrieval settings section', () => {
|
|
render(<ExternalKnowledgeSection {...defaultProps} />)
|
|
expect(screen.getByText(/form\.retrievalSetting\.title/i)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render external knowledge API section', () => {
|
|
render(<ExternalKnowledgeSection {...defaultProps} />)
|
|
expect(screen.getByText(/form\.externalKnowledgeAPI/i)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render external knowledge ID section', () => {
|
|
render(<ExternalKnowledgeSection {...defaultProps} />)
|
|
expect(screen.getByText(/form\.externalKnowledgeID/i)).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('External Knowledge API Info', () => {
|
|
it('should display external API name', () => {
|
|
render(<ExternalKnowledgeSection {...defaultProps} />)
|
|
expect(screen.getByText('My External API')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should display external API endpoint', () => {
|
|
render(<ExternalKnowledgeSection {...defaultProps} />)
|
|
expect(screen.getByText('https://api.external.example.com/v1')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render API connection icon', () => {
|
|
const { container } = render(<ExternalKnowledgeSection {...defaultProps} />)
|
|
// The ApiConnectionMod icon should be rendered
|
|
const icon = container.querySelector('svg')
|
|
expect(icon).toBeInTheDocument()
|
|
})
|
|
|
|
it('should display API name and endpoint in the same row', () => {
|
|
render(<ExternalKnowledgeSection {...defaultProps} />)
|
|
|
|
const apiName = screen.getByText('My External API')
|
|
const apiEndpoint = screen.getByText('https://api.external.example.com/v1')
|
|
|
|
// Both should be in the same container
|
|
expect(apiName.parentElement?.parentElement).toBe(apiEndpoint.parentElement?.parentElement)
|
|
})
|
|
})
|
|
|
|
describe('External Knowledge ID', () => {
|
|
it('should display external knowledge ID value', () => {
|
|
render(<ExternalKnowledgeSection {...defaultProps} />)
|
|
expect(screen.getByText('ext-knowledge-123')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render ID in a read-only display', () => {
|
|
render(<ExternalKnowledgeSection {...defaultProps} />)
|
|
|
|
const idElement = screen.getByText('ext-knowledge-123')
|
|
// The ID should be in a div with input-like styling, not an actual input
|
|
expect(idElement.tagName.toLowerCase()).toBe('div')
|
|
})
|
|
})
|
|
|
|
describe('Retrieval Settings', () => {
|
|
it('should pass topK to RetrievalSettings', () => {
|
|
render(<ExternalKnowledgeSection {...defaultProps} topK={10} />)
|
|
|
|
// RetrievalSettings should receive topK prop
|
|
// The exact rendering depends on RetrievalSettings component
|
|
})
|
|
|
|
it('should pass scoreThreshold to RetrievalSettings', () => {
|
|
render(<ExternalKnowledgeSection {...defaultProps} scoreThreshold={0.9} />)
|
|
|
|
// RetrievalSettings should receive scoreThreshold prop
|
|
})
|
|
|
|
it('should pass scoreThresholdEnabled to RetrievalSettings', () => {
|
|
render(<ExternalKnowledgeSection {...defaultProps} scoreThresholdEnabled={false} />)
|
|
|
|
// RetrievalSettings should receive scoreThresholdEnabled prop
|
|
})
|
|
|
|
it('should call handleSettingsChange when settings change', () => {
|
|
const handleSettingsChange = vi.fn()
|
|
render(<ExternalKnowledgeSection {...defaultProps} handleSettingsChange={handleSettingsChange} />)
|
|
|
|
// The handler should be properly passed to RetrievalSettings
|
|
// Actual interaction depends on RetrievalSettings implementation
|
|
})
|
|
})
|
|
|
|
describe('Dividers', () => {
|
|
it('should render dividers between sections', () => {
|
|
const { container } = render(<ExternalKnowledgeSection {...defaultProps} />)
|
|
|
|
const dividers = container.querySelectorAll('.bg-divider-subtle')
|
|
expect(dividers.length).toBeGreaterThanOrEqual(2)
|
|
})
|
|
})
|
|
|
|
describe('Props Updates', () => {
|
|
it('should update when currentDataset changes', () => {
|
|
const { rerender } = render(<ExternalKnowledgeSection {...defaultProps} />)
|
|
|
|
expect(screen.getByText('My External API')).toBeInTheDocument()
|
|
|
|
const updatedDataset = {
|
|
...mockDataset,
|
|
external_knowledge_info: {
|
|
...mockDataset.external_knowledge_info,
|
|
external_knowledge_api_name: 'Updated API Name',
|
|
},
|
|
}
|
|
|
|
rerender(<ExternalKnowledgeSection {...defaultProps} currentDataset={updatedDataset} />)
|
|
|
|
expect(screen.getByText('Updated API Name')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should update when external knowledge ID changes', () => {
|
|
const { rerender } = render(<ExternalKnowledgeSection {...defaultProps} />)
|
|
|
|
expect(screen.getByText('ext-knowledge-123')).toBeInTheDocument()
|
|
|
|
const updatedDataset = {
|
|
...mockDataset,
|
|
external_knowledge_info: {
|
|
...mockDataset.external_knowledge_info,
|
|
external_knowledge_id: 'new-ext-id-789',
|
|
},
|
|
}
|
|
|
|
rerender(<ExternalKnowledgeSection {...defaultProps} currentDataset={updatedDataset} />)
|
|
|
|
expect(screen.getByText('new-ext-id-789')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should update when API endpoint changes', () => {
|
|
const { rerender } = render(<ExternalKnowledgeSection {...defaultProps} />)
|
|
|
|
expect(screen.getByText('https://api.external.example.com/v1')).toBeInTheDocument()
|
|
|
|
const updatedDataset = {
|
|
...mockDataset,
|
|
external_knowledge_info: {
|
|
...mockDataset.external_knowledge_info,
|
|
external_knowledge_api_endpoint: 'https://new-api.example.com/v2',
|
|
},
|
|
}
|
|
|
|
rerender(<ExternalKnowledgeSection {...defaultProps} currentDataset={updatedDataset} />)
|
|
|
|
expect(screen.getByText('https://new-api.example.com/v2')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('Layout', () => {
|
|
it('should have consistent row layout', () => {
|
|
const { container } = render(<ExternalKnowledgeSection {...defaultProps} />)
|
|
|
|
// Check for flex gap-x-1 class on rows
|
|
const rows = container.querySelectorAll('.flex.gap-x-1')
|
|
expect(rows.length).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('should have consistent label width', () => {
|
|
const { container } = render(<ExternalKnowledgeSection {...defaultProps} />)
|
|
|
|
// Check for w-[180px] label containers
|
|
const labels = container.querySelectorAll('.w-\\[180px\\]')
|
|
expect(labels.length).toBeGreaterThan(0)
|
|
})
|
|
})
|
|
|
|
describe('Styling', () => {
|
|
it('should apply correct background to info displays', () => {
|
|
const { container } = render(<ExternalKnowledgeSection {...defaultProps} />)
|
|
|
|
// Info displays should have bg-components-input-bg-normal
|
|
const infoDisplays = container.querySelectorAll('.bg-components-input-bg-normal')
|
|
expect(infoDisplays.length).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('should apply rounded corners to info displays', () => {
|
|
const { container } = render(<ExternalKnowledgeSection {...defaultProps} />)
|
|
|
|
const roundedElements = container.querySelectorAll('.rounded-lg')
|
|
expect(roundedElements.length).toBeGreaterThan(0)
|
|
})
|
|
})
|
|
|
|
describe('Different External Knowledge Info', () => {
|
|
it('should handle long API names', () => {
|
|
const longNameDataset = {
|
|
...mockDataset,
|
|
external_knowledge_info: {
|
|
...mockDataset.external_knowledge_info,
|
|
external_knowledge_api_name: 'This is a very long external knowledge API name that should be truncated',
|
|
},
|
|
}
|
|
|
|
render(<ExternalKnowledgeSection {...defaultProps} currentDataset={longNameDataset} />)
|
|
|
|
expect(screen.getByText(/This is a very long external knowledge API name/)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should handle long API endpoints', () => {
|
|
const longEndpointDataset = {
|
|
...mockDataset,
|
|
external_knowledge_info: {
|
|
...mockDataset.external_knowledge_info,
|
|
external_knowledge_api_endpoint: 'https://api.very-long-domain-name.example.com/api/v1/external/knowledge',
|
|
},
|
|
}
|
|
|
|
render(<ExternalKnowledgeSection {...defaultProps} currentDataset={longEndpointDataset} />)
|
|
|
|
expect(screen.getByText(/https:\/\/api.very-long-domain-name.example.com/)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should handle special characters in API name', () => {
|
|
const specialCharDataset = {
|
|
...mockDataset,
|
|
external_knowledge_info: {
|
|
...mockDataset.external_knowledge_info,
|
|
external_knowledge_api_name: 'API & Service <Test>',
|
|
},
|
|
}
|
|
|
|
render(<ExternalKnowledgeSection {...defaultProps} currentDataset={specialCharDataset} />)
|
|
|
|
expect(screen.getByText('API & Service <Test>')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('RetrievalSettings Integration', () => {
|
|
it('should pass isInRetrievalSetting=true to RetrievalSettings', () => {
|
|
render(<ExternalKnowledgeSection {...defaultProps} />)
|
|
|
|
// The RetrievalSettings component should be rendered with isInRetrievalSetting=true
|
|
// This affects the component's layout/styling
|
|
})
|
|
|
|
it('should handle settings change for top_k', () => {
|
|
const handleSettingsChange = vi.fn()
|
|
render(<ExternalKnowledgeSection {...defaultProps} handleSettingsChange={handleSettingsChange} />)
|
|
|
|
// Find and interact with the top_k control in RetrievalSettings
|
|
// The exact interaction depends on RetrievalSettings implementation
|
|
})
|
|
|
|
it('should handle settings change for score_threshold', () => {
|
|
const handleSettingsChange = vi.fn()
|
|
render(<ExternalKnowledgeSection {...defaultProps} handleSettingsChange={handleSettingsChange} />)
|
|
|
|
// Find and interact with the score_threshold control in RetrievalSettings
|
|
})
|
|
|
|
it('should handle settings change for score_threshold_enabled', () => {
|
|
const handleSettingsChange = vi.fn()
|
|
render(<ExternalKnowledgeSection {...defaultProps} handleSettingsChange={handleSettingsChange} />)
|
|
|
|
// Find and interact with the score_threshold_enabled toggle in RetrievalSettings
|
|
})
|
|
})
|
|
|
|
describe('Accessibility', () => {
|
|
it('should have semantic structure', () => {
|
|
render(<ExternalKnowledgeSection {...defaultProps} />)
|
|
|
|
// Section labels should be present
|
|
expect(screen.getByText(/form\.retrievalSetting\.title/i)).toBeInTheDocument()
|
|
expect(screen.getByText(/form\.externalKnowledgeAPI/i)).toBeInTheDocument()
|
|
expect(screen.getByText(/form\.externalKnowledgeID/i)).toBeInTheDocument()
|
|
})
|
|
})
|
|
})
|