Files
dify/web/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool.spec.tsx
Joel 8cf1da96f5 chore: tests for app agent configures (#29789)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-17 16:39:53 +08:00

249 lines
7.6 KiB
TypeScript

import React from 'react'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import SettingBuiltInTool from './setting-built-in-tool'
import I18n from '@/context/i18n'
import { CollectionType, type Tool, type ToolParameter } from '@/app/components/tools/types'
const fetchModelToolList = jest.fn()
const fetchBuiltInToolList = jest.fn()
const fetchCustomToolList = jest.fn()
const fetchWorkflowToolList = jest.fn()
jest.mock('@/service/tools', () => ({
fetchModelToolList: (collectionName: string) => fetchModelToolList(collectionName),
fetchBuiltInToolList: (collectionName: string) => fetchBuiltInToolList(collectionName),
fetchCustomToolList: (collectionName: string) => fetchCustomToolList(collectionName),
fetchWorkflowToolList: (appId: string) => fetchWorkflowToolList(appId),
}))
type MockFormProps = {
value: Record<string, any>
onChange: (val: Record<string, any>) => void
}
let nextFormValue: Record<string, any> = {}
const FormMock = ({ value, onChange }: MockFormProps) => {
return (
<div data-testid="mock-form">
<div data-testid="form-value">{JSON.stringify(value)}</div>
<button
type="button"
onClick={() => onChange({ ...value, ...nextFormValue })}
>
update-form
</button>
</div>
)
}
jest.mock('@/app/components/header/account-setting/model-provider-page/model-modal/Form', () => ({
__esModule: true,
default: (props: MockFormProps) => <FormMock {...props} />,
}))
let pluginAuthClickValue = 'credential-from-plugin'
jest.mock('@/app/components/plugins/plugin-auth', () => ({
AuthCategory: { tool: 'tool' },
PluginAuthInAgent: (props: { onAuthorizationItemClick?: (id: string) => void }) => (
<div data-testid="plugin-auth">
<button type="button" onClick={() => props.onAuthorizationItemClick?.(pluginAuthClickValue)}>
choose-plugin-credential
</button>
</div>
),
}))
jest.mock('@/app/components/plugins/readme-panel/entrance', () => ({
ReadmeEntrance: ({ className }: { className?: string }) => <div className={className}>readme</div>,
}))
const createParameter = (overrides?: Partial<ToolParameter>): ToolParameter => ({
name: 'settingParam',
label: {
en_US: 'Setting Param',
zh_Hans: 'Setting Param',
},
human_description: {
en_US: 'desc',
zh_Hans: 'desc',
},
type: 'string',
form: 'config',
llm_description: '',
required: true,
multiple: false,
default: '',
...overrides,
})
const createTool = (overrides?: Partial<Tool>): Tool => ({
name: 'search',
author: 'tester',
label: {
en_US: 'Search Tool',
zh_Hans: 'Search Tool',
},
description: {
en_US: 'tool description',
zh_Hans: 'tool description',
},
parameters: [
createParameter({
name: 'infoParam',
label: {
en_US: 'Info Param',
zh_Hans: 'Info Param',
},
form: 'llm',
required: false,
}),
createParameter(),
],
labels: [],
output_schema: {},
...overrides,
})
const baseCollection = {
id: 'provider-1',
name: 'vendor/provider-1',
author: 'tester',
description: {
en_US: 'desc',
zh_Hans: 'desc',
},
icon: 'https://example.com/icon.png',
label: {
en_US: 'Provider Label',
zh_Hans: 'Provider Label',
},
type: CollectionType.builtIn,
team_credentials: {},
is_team_authorization: true,
allow_delete: true,
labels: [],
tools: [createTool()],
}
const renderComponent = (props?: Partial<React.ComponentProps<typeof SettingBuiltInTool>>) => {
const onHide = jest.fn()
const onSave = jest.fn()
const onAuthorizationItemClick = jest.fn()
const utils = render(
<I18n.Provider value={{ locale: 'en-US', i18n: {}, setLocaleOnClient: jest.fn() as any }}>
<SettingBuiltInTool
collection={baseCollection as any}
toolName="search"
isModel
setting={{ settingParam: 'value' }}
onHide={onHide}
onSave={onSave}
onAuthorizationItemClick={onAuthorizationItemClick}
{...props}
/>
</I18n.Provider>,
)
return {
...utils,
onHide,
onSave,
onAuthorizationItemClick,
}
}
describe('SettingBuiltInTool', () => {
beforeEach(() => {
jest.clearAllMocks()
nextFormValue = {}
pluginAuthClickValue = 'credential-from-plugin'
})
test('should fetch tool list when collection has no tools', async () => {
fetchModelToolList.mockResolvedValueOnce([createTool()])
renderComponent({
collection: {
...baseCollection,
tools: [],
},
})
await waitFor(() => {
expect(fetchModelToolList).toHaveBeenCalledTimes(1)
expect(fetchModelToolList).toHaveBeenCalledWith('vendor/provider-1')
})
expect(await screen.findByText('Search Tool')).toBeInTheDocument()
})
test('should switch between info and setting tabs', async () => {
renderComponent()
await waitFor(() => {
expect(screen.getByTestId('mock-form')).toBeInTheDocument()
})
await userEvent.click(screen.getByText('tools.setBuiltInTools.parameters'))
expect(screen.getByText('Info Param')).toBeInTheDocument()
await userEvent.click(screen.getByText('tools.setBuiltInTools.setting'))
expect(screen.getByTestId('mock-form')).toBeInTheDocument()
})
test('should call onSave with updated values when save button clicked', async () => {
const { onSave } = renderComponent()
await waitFor(() => expect(screen.getByTestId('mock-form')).toBeInTheDocument())
nextFormValue = { settingParam: 'updated' }
await userEvent.click(screen.getByRole('button', { name: 'update-form' }))
await userEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
expect(onSave).toHaveBeenCalledWith(expect.objectContaining({ settingParam: 'updated' }))
})
test('should keep save disabled until required field provided', async () => {
renderComponent({
setting: {},
})
await waitFor(() => expect(screen.getByTestId('mock-form')).toBeInTheDocument())
const saveButton = screen.getByRole('button', { name: 'common.operation.save' })
expect(saveButton).toBeDisabled()
nextFormValue = { settingParam: 'filled' }
await userEvent.click(screen.getByRole('button', { name: 'update-form' }))
expect(saveButton).not.toBeDisabled()
})
test('should call onHide when cancel button is pressed', async () => {
const { onHide } = renderComponent()
await waitFor(() => expect(screen.getByTestId('mock-form')).toBeInTheDocument())
await userEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
expect(onHide).toHaveBeenCalled()
})
test('should trigger authorization callback from plugin auth section', async () => {
const { onAuthorizationItemClick } = renderComponent()
await userEvent.click(screen.getByRole('button', { name: 'choose-plugin-credential' }))
expect(onAuthorizationItemClick).toHaveBeenCalledWith('credential-from-plugin')
})
test('should call onHide when back button is clicked', async () => {
const { onHide } = renderComponent({
showBackButton: true,
})
await userEvent.click(screen.getByText('plugin.detailPanel.operation.back'))
expect(onHide).toHaveBeenCalled()
})
test('should load workflow tools when workflow collection is provided', async () => {
fetchWorkflowToolList.mockResolvedValueOnce([createTool({
name: 'workflow-tool',
})])
renderComponent({
collection: {
...baseCollection,
type: CollectionType.workflow,
tools: [],
id: 'workflow-1',
} as any,
isBuiltIn: false,
isModel: false,
})
await waitFor(() => {
expect(fetchWorkflowToolList).toHaveBeenCalledWith('workflow-1')
})
})
})