mirror of
https://github.com/langgenius/dify.git
synced 2026-04-05 09:49:25 +08:00
Co-authored-by: CodingOnStar <hanxujiang@dify.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
916 lines
27 KiB
TypeScript
916 lines
27 KiB
TypeScript
import type { RetrievalConfig } from '@/types/app'
|
|
import { fireEvent, render, screen } from '@testing-library/react'
|
|
import { RerankingModeEnum, WeightedScoreEnum } from '@/models/datasets'
|
|
import { RETRIEVE_METHOD } from '@/types/app'
|
|
import RetrievalParamConfig from './index'
|
|
|
|
vi.mock('react-i18next', () => ({
|
|
useTranslation: () => ({
|
|
t: (key: string) => key,
|
|
}),
|
|
}))
|
|
|
|
const mockNotify = vi.fn()
|
|
vi.mock('@/app/components/base/toast', () => ({
|
|
default: {
|
|
notify: (params: { type: string, message: string }) => mockNotify(params),
|
|
},
|
|
}))
|
|
|
|
let mockCurrentModel: { model: string, provider: string } | null = {
|
|
model: 'rerank-model',
|
|
provider: 'rerank-provider',
|
|
}
|
|
|
|
vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
|
|
useModelListAndDefaultModel: () => ({
|
|
modelList: [
|
|
{
|
|
provider: 'rerank-provider',
|
|
models: [{ model: 'rerank-model', label: { en_US: 'Rerank Model' } }],
|
|
},
|
|
],
|
|
defaultModel: { provider: 'rerank-provider', model: 'rerank-model' },
|
|
}),
|
|
useCurrentProviderAndModel: () => ({
|
|
currentModel: mockCurrentModel,
|
|
currentProvider: mockCurrentModel ? { provider: 'rerank-provider' } : null,
|
|
}),
|
|
}))
|
|
|
|
vi.mock('@/app/components/header/account-setting/model-provider-page/model-selector', () => ({
|
|
default: ({ onSelect, defaultModel }: { onSelect: (v: { provider: string, model: string }) => void, defaultModel?: { provider: string, model: string } }) => (
|
|
<div data-testid="model-selector" data-default-model={defaultModel ? JSON.stringify(defaultModel) : ''}>
|
|
<button
|
|
data-testid="select-model-btn"
|
|
onClick={() => onSelect({ provider: 'new-provider', model: 'new-model' })}
|
|
>
|
|
Select Model
|
|
</button>
|
|
</div>
|
|
),
|
|
}))
|
|
|
|
vi.mock('@/app/components/app/configuration/dataset-config/params-config/weighted-score', () => ({
|
|
default: ({ value, onChange }: { value: { value: number[] }, onChange: (v: { value: number[] }) => void }) => (
|
|
<div data-testid="weighted-score" data-value={JSON.stringify(value)}>
|
|
<button
|
|
data-testid="change-weights-btn"
|
|
onClick={() => onChange({ value: [0.6, 0.4] })}
|
|
>
|
|
Change Weights
|
|
</button>
|
|
</div>
|
|
),
|
|
}))
|
|
|
|
vi.mock('@/app/components/base/param-item/top-k-item', () => ({
|
|
default: ({ value, onChange }: { value: number, onChange: (key: string, v: number) => void }) => (
|
|
<div data-testid="top-k-item" data-value={value}>
|
|
<button
|
|
data-testid="change-top-k-btn"
|
|
onClick={() => onChange('top_k', 10)}
|
|
>
|
|
Change TopK
|
|
</button>
|
|
</div>
|
|
),
|
|
}))
|
|
|
|
vi.mock('@/app/components/base/param-item/score-threshold-item', () => ({
|
|
default: ({ value, onChange, enable, hasSwitch, onSwitchChange }: {
|
|
value: number
|
|
onChange: (key: string, v: number) => void
|
|
enable: boolean
|
|
hasSwitch: boolean
|
|
onSwitchChange?: (key: string, v: boolean) => void
|
|
}) => (
|
|
<div
|
|
data-testid="score-threshold-item"
|
|
data-value={value}
|
|
data-enabled={enable}
|
|
data-has-switch={hasSwitch}
|
|
>
|
|
<button
|
|
data-testid="change-score-btn"
|
|
onClick={() => onChange('score_threshold', 0.8)}
|
|
>
|
|
Change Score
|
|
</button>
|
|
{hasSwitch && onSwitchChange && (
|
|
<button
|
|
data-testid="toggle-score-switch-btn"
|
|
onClick={() => onSwitchChange('score_threshold_enabled', !enable)}
|
|
>
|
|
Toggle Score Switch
|
|
</button>
|
|
)}
|
|
</div>
|
|
),
|
|
}))
|
|
|
|
vi.mock('@/app/components/base/radio-card', () => ({
|
|
default: ({ isChosen, onChosen, title, description }: {
|
|
isChosen: boolean
|
|
onChosen: () => void
|
|
title: string
|
|
description: string
|
|
}) => (
|
|
<div
|
|
data-testid="radio-card"
|
|
data-chosen={isChosen}
|
|
data-title={title}
|
|
onClick={onChosen}
|
|
>
|
|
{title}
|
|
<span data-testid="radio-description">{description}</span>
|
|
</div>
|
|
),
|
|
}))
|
|
|
|
vi.mock('@/app/components/base/switch', () => ({
|
|
default: ({ defaultValue, onChange }: { defaultValue: boolean, onChange: (v: boolean) => void }) => (
|
|
<button
|
|
data-testid="rerank-switch"
|
|
data-checked={defaultValue}
|
|
onClick={() => onChange(!defaultValue)}
|
|
>
|
|
Switch
|
|
</button>
|
|
),
|
|
}))
|
|
|
|
vi.mock('@/app/components/base/tooltip', () => ({
|
|
default: ({ popupContent }: { popupContent: React.ReactNode }) => (
|
|
<div data-testid="tooltip">{popupContent}</div>
|
|
),
|
|
}))
|
|
|
|
describe('RetrievalParamConfig', () => {
|
|
const createDefaultConfig = (overrides?: Partial<RetrievalConfig>): RetrievalConfig => ({
|
|
search_method: RETRIEVE_METHOD.semantic,
|
|
reranking_enable: true,
|
|
reranking_model: {
|
|
reranking_provider_name: 'rerank-provider',
|
|
reranking_model_name: 'rerank-model',
|
|
},
|
|
top_k: 5,
|
|
score_threshold_enabled: true,
|
|
score_threshold: 0.5,
|
|
reranking_mode: RerankingModeEnum.RerankingModel,
|
|
...overrides,
|
|
})
|
|
|
|
const mockOnChange = vi.fn()
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
mockCurrentModel = { model: 'rerank-model', provider: 'rerank-provider' }
|
|
})
|
|
|
|
describe('Semantic Search Mode', () => {
|
|
it('should render rerank switch for semantic search', () => {
|
|
const config = createDefaultConfig()
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.semantic}
|
|
value={config}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByTestId('rerank-switch')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render model selector when reranking is enabled', () => {
|
|
const config = createDefaultConfig({ reranking_enable: true })
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.semantic}
|
|
value={config}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByTestId('model-selector')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should not render model selector when reranking is disabled', () => {
|
|
const config = createDefaultConfig({ reranking_enable: false })
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.semantic}
|
|
value={config}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.queryByTestId('model-selector')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should render TopK item', () => {
|
|
const config = createDefaultConfig()
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.semantic}
|
|
value={config}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByTestId('top-k-item')).toBeInTheDocument()
|
|
expect(screen.getByTestId('top-k-item')).toHaveAttribute('data-value', '5')
|
|
})
|
|
|
|
it('should render score threshold item when reranking is enabled', () => {
|
|
const config = createDefaultConfig({ reranking_enable: true })
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.semantic}
|
|
value={config}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByTestId('score-threshold-item')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should toggle reranking enable', () => {
|
|
const config = createDefaultConfig({ reranking_enable: true })
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.semantic}
|
|
value={config}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
fireEvent.click(screen.getByTestId('rerank-switch'))
|
|
|
|
expect(mockOnChange).toHaveBeenCalledWith({
|
|
...config,
|
|
reranking_enable: false,
|
|
})
|
|
})
|
|
|
|
it('should show error toast when enabling rerank without model', () => {
|
|
mockCurrentModel = null
|
|
const config = createDefaultConfig({ reranking_enable: false })
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.semantic}
|
|
value={config}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
fireEvent.click(screen.getByTestId('rerank-switch'))
|
|
|
|
expect(mockNotify).toHaveBeenCalledWith({
|
|
type: 'error',
|
|
message: 'errorMsg.rerankModelRequired',
|
|
})
|
|
})
|
|
|
|
it('should update reranking model on selection', () => {
|
|
const config = createDefaultConfig({ reranking_enable: true })
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.semantic}
|
|
value={config}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
fireEvent.click(screen.getByTestId('select-model-btn'))
|
|
|
|
expect(mockOnChange).toHaveBeenCalledWith({
|
|
...config,
|
|
reranking_model: {
|
|
reranking_provider_name: 'new-provider',
|
|
reranking_model_name: 'new-model',
|
|
},
|
|
})
|
|
})
|
|
|
|
it('should update top_k value', () => {
|
|
const config = createDefaultConfig()
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.semantic}
|
|
value={config}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
fireEvent.click(screen.getByTestId('change-top-k-btn'))
|
|
|
|
expect(mockOnChange).toHaveBeenCalledWith({
|
|
...config,
|
|
top_k: 10,
|
|
})
|
|
})
|
|
|
|
it('should update score threshold value', () => {
|
|
const config = createDefaultConfig({ reranking_enable: true })
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.semantic}
|
|
value={config}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
fireEvent.click(screen.getByTestId('change-score-btn'))
|
|
|
|
expect(mockOnChange).toHaveBeenCalledWith({
|
|
...config,
|
|
score_threshold: 0.8,
|
|
})
|
|
})
|
|
|
|
it('should toggle score threshold enabled', () => {
|
|
const config = createDefaultConfig({ reranking_enable: true, score_threshold_enabled: true })
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.semantic}
|
|
value={config}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
fireEvent.click(screen.getByTestId('toggle-score-switch-btn'))
|
|
|
|
expect(mockOnChange).toHaveBeenCalledWith({
|
|
...config,
|
|
score_threshold_enabled: false,
|
|
})
|
|
})
|
|
|
|
it('should show multimodal tip when showMultiModalTip is true and reranking enabled', () => {
|
|
const config = createDefaultConfig({ reranking_enable: true })
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.semantic}
|
|
value={config}
|
|
showMultiModalTip={true}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByText('form.retrievalSetting.multiModalTip')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should not show multimodal tip when showMultiModalTip is false', () => {
|
|
const config = createDefaultConfig({ reranking_enable: true })
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.semantic}
|
|
value={config}
|
|
showMultiModalTip={false}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.queryByText('form.retrievalSetting.multiModalTip')).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('Full Text Search Mode', () => {
|
|
it('should render rerank switch for full text search', () => {
|
|
const config = createDefaultConfig({ search_method: RETRIEVE_METHOD.fullText })
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.fullText}
|
|
value={config}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByTestId('rerank-switch')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should hide score threshold when reranking is disabled for full text search', () => {
|
|
const config = createDefaultConfig({
|
|
search_method: RETRIEVE_METHOD.fullText,
|
|
reranking_enable: false,
|
|
})
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.fullText}
|
|
value={config}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.queryByTestId('score-threshold-item')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should show score threshold when reranking is enabled for full text search', () => {
|
|
const config = createDefaultConfig({
|
|
search_method: RETRIEVE_METHOD.fullText,
|
|
reranking_enable: true,
|
|
})
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.fullText}
|
|
value={config}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByTestId('score-threshold-item')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('Keyword Search Mode (Economical)', () => {
|
|
it('should not render rerank switch for keyword search', () => {
|
|
const config = createDefaultConfig()
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.keywordSearch}
|
|
value={config}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.queryByTestId('rerank-switch')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should not render model selector for keyword search', () => {
|
|
const config = createDefaultConfig({ reranking_enable: true })
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.keywordSearch}
|
|
value={config}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.queryByTestId('model-selector')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should render TopK item for keyword search', () => {
|
|
const config = createDefaultConfig()
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.keywordSearch}
|
|
value={config}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByTestId('top-k-item')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should not render score threshold for keyword search', () => {
|
|
const config = createDefaultConfig()
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.keywordSearch}
|
|
value={config}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.queryByTestId('score-threshold-item')).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('Hybrid Search Mode', () => {
|
|
const hybridConfig = createDefaultConfig({
|
|
search_method: RETRIEVE_METHOD.hybrid,
|
|
reranking_mode: RerankingModeEnum.RerankingModel,
|
|
})
|
|
|
|
it('should render radio cards for reranking mode selection', () => {
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.hybrid}
|
|
value={hybridConfig}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
const radioCards = screen.getAllByTestId('radio-card')
|
|
expect(radioCards).toHaveLength(2)
|
|
})
|
|
|
|
it('should have WeightedScore option', () => {
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.hybrid}
|
|
value={hybridConfig}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByText('weightedScore.title')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should have RerankingModel option', () => {
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.hybrid}
|
|
value={hybridConfig}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByText('modelProvider.rerankModel.key')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should show model selector when RerankingModel mode is selected', () => {
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.hybrid}
|
|
value={hybridConfig}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByTestId('model-selector')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should show WeightedScore component when WeightedScore mode is selected', () => {
|
|
const weightedConfig = createDefaultConfig({
|
|
search_method: RETRIEVE_METHOD.hybrid,
|
|
reranking_mode: RerankingModeEnum.WeightedScore,
|
|
weights: {
|
|
weight_type: WeightedScoreEnum.Customized,
|
|
vector_setting: {
|
|
vector_weight: 0.7,
|
|
embedding_provider_name: '',
|
|
embedding_model_name: '',
|
|
},
|
|
keyword_setting: {
|
|
keyword_weight: 0.3,
|
|
},
|
|
},
|
|
})
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.hybrid}
|
|
value={weightedConfig}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByTestId('weighted-score')).toBeInTheDocument()
|
|
expect(screen.queryByTestId('model-selector')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should change reranking mode to WeightedScore', () => {
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.hybrid}
|
|
value={hybridConfig}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
const radioCards = screen.getAllByTestId('radio-card')
|
|
const weightedScoreCard = radioCards.find(card => card.getAttribute('data-title') === 'weightedScore.title')
|
|
fireEvent.click(weightedScoreCard!)
|
|
|
|
expect(mockOnChange).toHaveBeenCalled()
|
|
const calledWith = mockOnChange.mock.calls[0][0]
|
|
expect(calledWith.reranking_mode).toBe(RerankingModeEnum.WeightedScore)
|
|
expect(calledWith.weights).toBeDefined()
|
|
})
|
|
|
|
it('should not call onChange when clicking already selected mode', () => {
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.hybrid}
|
|
value={hybridConfig}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
const radioCards = screen.getAllByTestId('radio-card')
|
|
const rerankModelCard = radioCards.find(card => card.getAttribute('data-title') === 'modelProvider.rerankModel.key')
|
|
fireEvent.click(rerankModelCard!)
|
|
|
|
expect(mockOnChange).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should show error toast when switching to RerankingModel without model', () => {
|
|
mockCurrentModel = null
|
|
const weightedConfig = createDefaultConfig({
|
|
search_method: RETRIEVE_METHOD.hybrid,
|
|
reranking_mode: RerankingModeEnum.WeightedScore,
|
|
weights: {
|
|
weight_type: WeightedScoreEnum.Customized,
|
|
vector_setting: {
|
|
vector_weight: 0.7,
|
|
embedding_provider_name: '',
|
|
embedding_model_name: '',
|
|
},
|
|
keyword_setting: {
|
|
keyword_weight: 0.3,
|
|
},
|
|
},
|
|
})
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.hybrid}
|
|
value={weightedConfig}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
const radioCards = screen.getAllByTestId('radio-card')
|
|
const rerankModelCard = radioCards.find(card => card.getAttribute('data-title') === 'modelProvider.rerankModel.key')
|
|
fireEvent.click(rerankModelCard!)
|
|
|
|
expect(mockNotify).toHaveBeenCalledWith({
|
|
type: 'error',
|
|
message: 'errorMsg.rerankModelRequired',
|
|
})
|
|
})
|
|
|
|
it('should update weights when WeightedScore changes', () => {
|
|
const weightedConfig = createDefaultConfig({
|
|
search_method: RETRIEVE_METHOD.hybrid,
|
|
reranking_mode: RerankingModeEnum.WeightedScore,
|
|
weights: {
|
|
weight_type: WeightedScoreEnum.Customized,
|
|
vector_setting: {
|
|
vector_weight: 0.7,
|
|
embedding_provider_name: '',
|
|
embedding_model_name: '',
|
|
},
|
|
keyword_setting: {
|
|
keyword_weight: 0.3,
|
|
},
|
|
},
|
|
})
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.hybrid}
|
|
value={weightedConfig}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
fireEvent.click(screen.getByTestId('change-weights-btn'))
|
|
|
|
expect(mockOnChange).toHaveBeenCalled()
|
|
const calledWith = mockOnChange.mock.calls[0][0]
|
|
expect(calledWith.weights.vector_setting.vector_weight).toBe(0.6)
|
|
expect(calledWith.weights.keyword_setting.keyword_weight).toBe(0.4)
|
|
})
|
|
|
|
it('should render TopK and score threshold for hybrid search', () => {
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.hybrid}
|
|
value={hybridConfig}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByTestId('top-k-item')).toBeInTheDocument()
|
|
expect(screen.getByTestId('score-threshold-item')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should update top_k for hybrid search', () => {
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.hybrid}
|
|
value={hybridConfig}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
fireEvent.click(screen.getByTestId('change-top-k-btn'))
|
|
|
|
expect(mockOnChange).toHaveBeenCalledWith({
|
|
...hybridConfig,
|
|
top_k: 10,
|
|
})
|
|
})
|
|
|
|
it('should update score threshold for hybrid search', () => {
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.hybrid}
|
|
value={hybridConfig}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
fireEvent.click(screen.getByTestId('change-score-btn'))
|
|
|
|
expect(mockOnChange).toHaveBeenCalledWith({
|
|
...hybridConfig,
|
|
score_threshold: 0.8,
|
|
})
|
|
})
|
|
|
|
it('should toggle score threshold enabled for hybrid search', () => {
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.hybrid}
|
|
value={hybridConfig}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
fireEvent.click(screen.getByTestId('toggle-score-switch-btn'))
|
|
|
|
expect(mockOnChange).toHaveBeenCalledWith({
|
|
...hybridConfig,
|
|
score_threshold_enabled: false,
|
|
})
|
|
})
|
|
|
|
it('should show multimodal tip for hybrid search with RerankingModel', () => {
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.hybrid}
|
|
value={hybridConfig}
|
|
showMultiModalTip={true}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByText('form.retrievalSetting.multiModalTip')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should not show multimodal tip for hybrid search with WeightedScore', () => {
|
|
const weightedConfig = createDefaultConfig({
|
|
search_method: RETRIEVE_METHOD.hybrid,
|
|
reranking_mode: RerankingModeEnum.WeightedScore,
|
|
weights: {
|
|
weight_type: WeightedScoreEnum.Customized,
|
|
vector_setting: {
|
|
vector_weight: 0.7,
|
|
embedding_provider_name: '',
|
|
embedding_model_name: '',
|
|
},
|
|
keyword_setting: {
|
|
keyword_weight: 0.3,
|
|
},
|
|
},
|
|
})
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.hybrid}
|
|
value={weightedConfig}
|
|
showMultiModalTip={true}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.queryByText('form.retrievalSetting.multiModalTip')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should not render rerank switch for hybrid search', () => {
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.hybrid}
|
|
value={hybridConfig}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.queryByTestId('rerank-switch')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should update model selection for hybrid search', () => {
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.hybrid}
|
|
value={hybridConfig}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
fireEvent.click(screen.getByTestId('select-model-btn'))
|
|
|
|
expect(mockOnChange).toHaveBeenCalledWith({
|
|
...hybridConfig,
|
|
reranking_model: {
|
|
reranking_provider_name: 'new-provider',
|
|
reranking_model_name: 'new-model',
|
|
},
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Tooltip', () => {
|
|
it('should render tooltip with rerank model tip', () => {
|
|
const config = createDefaultConfig()
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.semantic}
|
|
value={config}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByTestId('tooltip')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('Rerank Model Label', () => {
|
|
it('should display rerank model label', () => {
|
|
const config = createDefaultConfig()
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.semantic}
|
|
value={config}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByText('modelProvider.rerankModel.key')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('Default weights initialization', () => {
|
|
it('should initialize default weights when switching to WeightedScore without existing weights', () => {
|
|
const configWithoutWeights = createDefaultConfig({
|
|
search_method: RETRIEVE_METHOD.hybrid,
|
|
reranking_mode: RerankingModeEnum.RerankingModel,
|
|
weights: undefined,
|
|
})
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.hybrid}
|
|
value={configWithoutWeights}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
const radioCards = screen.getAllByTestId('radio-card')
|
|
const weightedScoreCard = radioCards.find(card => card.getAttribute('data-title') === 'weightedScore.title')
|
|
fireEvent.click(weightedScoreCard!)
|
|
|
|
expect(mockOnChange).toHaveBeenCalled()
|
|
const calledWith = mockOnChange.mock.calls[0][0]
|
|
expect(calledWith.weights).toBeDefined()
|
|
expect(calledWith.weights.weight_type).toBe(WeightedScoreEnum.Customized)
|
|
})
|
|
|
|
it('should preserve existing weights when switching to WeightedScore', () => {
|
|
const configWithWeights = createDefaultConfig({
|
|
search_method: RETRIEVE_METHOD.hybrid,
|
|
reranking_mode: RerankingModeEnum.RerankingModel,
|
|
weights: {
|
|
weight_type: WeightedScoreEnum.Customized,
|
|
vector_setting: {
|
|
vector_weight: 0.8,
|
|
embedding_provider_name: 'test-provider',
|
|
embedding_model_name: 'test-model',
|
|
},
|
|
keyword_setting: {
|
|
keyword_weight: 0.2,
|
|
},
|
|
},
|
|
})
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.hybrid}
|
|
value={configWithWeights}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
const radioCards = screen.getAllByTestId('radio-card')
|
|
const weightedScoreCard = radioCards.find(card => card.getAttribute('data-title') === 'weightedScore.title')
|
|
fireEvent.click(weightedScoreCard!)
|
|
|
|
expect(mockOnChange).toHaveBeenCalled()
|
|
const calledWith = mockOnChange.mock.calls[0][0]
|
|
expect(calledWith.weights.vector_setting.vector_weight).toBe(0.8)
|
|
})
|
|
})
|
|
|
|
describe('Model Selector Default Model', () => {
|
|
it('should pass correct default model to ModelSelector', () => {
|
|
const config = createDefaultConfig({
|
|
reranking_enable: true,
|
|
reranking_model: {
|
|
reranking_provider_name: 'custom-provider',
|
|
reranking_model_name: 'custom-model',
|
|
},
|
|
})
|
|
render(
|
|
<RetrievalParamConfig
|
|
type={RETRIEVE_METHOD.semantic}
|
|
value={config}
|
|
onChange={mockOnChange}
|
|
/>,
|
|
)
|
|
|
|
const modelSelector = screen.getByTestId('model-selector')
|
|
const defaultModel = JSON.parse(modelSelector.getAttribute('data-default-model') || '{}')
|
|
expect(defaultModel.provider).toBe('custom-provider')
|
|
expect(defaultModel.model).toBe('custom-model')
|
|
})
|
|
})
|
|
})
|