mirror of
https://github.com/langgenius/dify.git
synced 2026-04-05 19:59:21 +08:00
239 lines
6.8 KiB
TypeScript
239 lines
6.8 KiB
TypeScript
import type { Model, ModelItem } from '../declarations'
|
|
import { fireEvent, render, screen } from '@testing-library/react'
|
|
import { tooltipManager } from '@/app/components/base/tooltip/TooltipManager'
|
|
import {
|
|
ConfigurationMethodEnum,
|
|
ModelFeatureEnum,
|
|
ModelStatusEnum,
|
|
ModelTypeEnum,
|
|
} from '../declarations'
|
|
import Popup from './popup'
|
|
|
|
let mockLanguage = 'en_US'
|
|
|
|
const mockSetShowAccountSettingModal = vi.hoisted(() => vi.fn())
|
|
vi.mock('@/context/modal-context', () => ({
|
|
useModalContext: () => ({
|
|
setShowAccountSettingModal: mockSetShowAccountSettingModal,
|
|
}),
|
|
}))
|
|
|
|
const mockSupportFunctionCall = vi.hoisted(() => vi.fn())
|
|
vi.mock('@/utils/tool-call', () => ({
|
|
supportFunctionCall: mockSupportFunctionCall,
|
|
}))
|
|
|
|
vi.mock('../hooks', async () => {
|
|
const actual = await vi.importActual<typeof import('../hooks')>('../hooks')
|
|
return {
|
|
...actual,
|
|
useLanguage: () => mockLanguage,
|
|
}
|
|
})
|
|
|
|
vi.mock('./popup-item', () => ({
|
|
default: ({ model }: { model: Model }) => <div>{model.provider}</div>,
|
|
}))
|
|
|
|
const makeModelItem = (overrides: Partial<ModelItem> = {}): ModelItem => ({
|
|
model: 'gpt-4',
|
|
label: { en_US: 'GPT-4', zh_Hans: 'GPT-4' },
|
|
model_type: ModelTypeEnum.textGeneration,
|
|
fetch_from: ConfigurationMethodEnum.predefinedModel,
|
|
status: ModelStatusEnum.active,
|
|
model_properties: {},
|
|
load_balancing_enabled: false,
|
|
...overrides,
|
|
})
|
|
|
|
const makeModel = (overrides: Partial<Model> = {}): Model => ({
|
|
provider: 'openai',
|
|
icon_small: { en_US: '', zh_Hans: '' },
|
|
label: { en_US: 'OpenAI', zh_Hans: 'OpenAI' },
|
|
models: [makeModelItem()],
|
|
status: ModelStatusEnum.active,
|
|
...overrides,
|
|
})
|
|
|
|
describe('Popup', () => {
|
|
let closeActiveTooltipSpy: ReturnType<typeof vi.spyOn>
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
mockLanguage = 'en_US'
|
|
mockSupportFunctionCall.mockReturnValue(true)
|
|
closeActiveTooltipSpy = vi.spyOn(tooltipManager, 'closeActiveTooltip')
|
|
})
|
|
|
|
it('should filter models by search and allow clearing search', () => {
|
|
render(
|
|
<Popup
|
|
modelList={[makeModel()]}
|
|
onSelect={vi.fn()}
|
|
onHide={vi.fn()}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByText('openai')).toBeInTheDocument()
|
|
|
|
const input = screen.getByPlaceholderText('datasetSettings.form.searchModel')
|
|
fireEvent.change(input, { target: { value: 'not-found' } })
|
|
expect(screen.getByText('No model found for “not-found”')).toBeInTheDocument()
|
|
|
|
fireEvent.change(input, { target: { value: '' } })
|
|
expect((input as HTMLInputElement).value).toBe('')
|
|
expect(screen.getByText('openai')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should filter by scope features including toolCall and non-toolCall checks', () => {
|
|
const modelList = [
|
|
makeModel({ models: [makeModelItem({ features: [ModelFeatureEnum.toolCall, ModelFeatureEnum.vision] })] }),
|
|
]
|
|
|
|
// When tool-call support is missing, it should be filtered out.
|
|
mockSupportFunctionCall.mockReturnValue(false)
|
|
const { unmount } = render(
|
|
<Popup
|
|
modelList={modelList}
|
|
onSelect={vi.fn()}
|
|
onHide={vi.fn()}
|
|
scopeFeatures={[ModelFeatureEnum.toolCall, ModelFeatureEnum.vision]}
|
|
/>,
|
|
)
|
|
expect(screen.getByText('No model found for “”')).toBeInTheDocument()
|
|
|
|
// When tool-call support exists, the non-toolCall feature check should also pass.
|
|
unmount()
|
|
mockSupportFunctionCall.mockReturnValue(true)
|
|
const { unmount: unmount2 } = render(
|
|
<Popup
|
|
modelList={modelList}
|
|
onSelect={vi.fn()}
|
|
onHide={vi.fn()}
|
|
scopeFeatures={[ModelFeatureEnum.toolCall, ModelFeatureEnum.vision]}
|
|
/>,
|
|
)
|
|
expect(screen.getByText('openai')).toBeInTheDocument()
|
|
|
|
unmount2()
|
|
const { unmount: unmount3 } = render(
|
|
<Popup
|
|
modelList={modelList}
|
|
onSelect={vi.fn()}
|
|
onHide={vi.fn()}
|
|
scopeFeatures={[ModelFeatureEnum.vision]}
|
|
/>,
|
|
)
|
|
expect(screen.getByText('openai')).toBeInTheDocument()
|
|
|
|
// When features are missing, non-toolCall feature checks should fail.
|
|
unmount3()
|
|
render(
|
|
<Popup
|
|
modelList={[makeModel({ models: [makeModelItem({ features: undefined })] })]}
|
|
onSelect={vi.fn()}
|
|
onHide={vi.fn()}
|
|
scopeFeatures={[ModelFeatureEnum.vision]}
|
|
/>,
|
|
)
|
|
expect(screen.getByText('No model found for “”')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should match labels from other languages when current language key is missing', () => {
|
|
mockLanguage = 'fr_FR'
|
|
|
|
render(
|
|
<Popup
|
|
modelList={[makeModel()]}
|
|
onSelect={vi.fn()}
|
|
onHide={vi.fn()}
|
|
/>,
|
|
)
|
|
|
|
fireEvent.change(
|
|
screen.getByPlaceholderText('datasetSettings.form.searchModel'),
|
|
{ target: { value: 'gpt' } },
|
|
)
|
|
|
|
expect(screen.getByText('openai')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should filter out model when features array exists but does not include required scopeFeature', () => {
|
|
const modelWithToolCallOnly = makeModel({
|
|
models: [makeModelItem({ features: [ModelFeatureEnum.toolCall] })],
|
|
})
|
|
|
|
render(
|
|
<Popup
|
|
modelList={[modelWithToolCallOnly]}
|
|
onSelect={vi.fn()}
|
|
onHide={vi.fn()}
|
|
scopeFeatures={[ModelFeatureEnum.vision]}
|
|
/>,
|
|
)
|
|
|
|
// The model item should be filtered out because it has toolCall but not vision
|
|
expect(screen.queryByText('openai')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should close tooltip on scroll', () => {
|
|
const { container } = render(
|
|
<Popup
|
|
modelList={[makeModel()]}
|
|
onSelect={vi.fn()}
|
|
onHide={vi.fn()}
|
|
/>,
|
|
)
|
|
|
|
fireEvent.scroll(container.firstElementChild as HTMLElement)
|
|
expect(closeActiveTooltipSpy).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should open provider settings when clicking footer link', () => {
|
|
render(
|
|
<Popup
|
|
modelList={[makeModel()]}
|
|
onSelect={vi.fn()}
|
|
onHide={vi.fn()}
|
|
/>,
|
|
)
|
|
|
|
fireEvent.click(screen.getByText('common.model.settingsLink'))
|
|
|
|
expect(mockSetShowAccountSettingModal).toHaveBeenCalledWith({
|
|
payload: 'provider',
|
|
})
|
|
})
|
|
|
|
it('should call onHide when footer settings link is clicked', () => {
|
|
const mockOnHide = vi.fn()
|
|
render(
|
|
<Popup
|
|
modelList={[makeModel()]}
|
|
onSelect={vi.fn()}
|
|
onHide={mockOnHide}
|
|
/>,
|
|
)
|
|
|
|
fireEvent.click(screen.getByText('common.model.settingsLink'))
|
|
|
|
expect(mockOnHide).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should match model label when searchText is non-empty and label key exists for current language', () => {
|
|
render(
|
|
<Popup
|
|
modelList={[makeModel()]}
|
|
onSelect={vi.fn()}
|
|
onHide={vi.fn()}
|
|
/>,
|
|
)
|
|
|
|
// GPT-4 label has en_US key, so modelItem.label[language] is defined
|
|
const input = screen.getByPlaceholderText('datasetSettings.form.searchModel')
|
|
fireEvent.change(input, { target: { value: 'gpt' } })
|
|
|
|
expect(screen.getByText('openai')).toBeInTheDocument()
|
|
})
|
|
})
|