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>
This commit is contained in:
Joel
2025-12-17 16:39:53 +08:00
committed by GitHub
parent 4fce99379e
commit 8cf1da96f5
5 changed files with 849 additions and 1 deletions

View File

@@ -0,0 +1,112 @@
import React from 'react'
import { act, fireEvent, render, screen } from '@testing-library/react'
import AgentSetting from './index'
import { MAX_ITERATIONS_NUM } from '@/config'
import type { AgentConfig } from '@/models/debug'
jest.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}))
jest.mock('ahooks', () => {
const actual = jest.requireActual('ahooks')
return {
...actual,
useClickAway: jest.fn(),
}
})
jest.mock('react-slider', () => (props: { className?: string; min?: number; max?: number; value: number; onChange: (value: number) => void }) => (
<input
type="range"
className={props.className}
min={props.min}
max={props.max}
value={props.value}
onChange={e => props.onChange(Number(e.target.value))}
/>
))
const basePayload = {
enabled: true,
strategy: 'react',
max_iteration: 5,
tools: [],
}
const renderModal = (props?: Partial<React.ComponentProps<typeof AgentSetting>>) => {
const onCancel = jest.fn()
const onSave = jest.fn()
const utils = render(
<AgentSetting
isChatModel
payload={basePayload as AgentConfig}
isFunctionCall={false}
onCancel={onCancel}
onSave={onSave}
{...props}
/>,
)
return { ...utils, onCancel, onSave }
}
describe('AgentSetting', () => {
test('should render agent mode description and default prompt section when not function call', () => {
renderModal()
expect(screen.getByText('appDebug.agent.agentMode')).toBeInTheDocument()
expect(screen.getByText('appDebug.agent.agentModeType.ReACT')).toBeInTheDocument()
expect(screen.getByText('tools.builtInPromptTitle')).toBeInTheDocument()
})
test('should display function call mode when isFunctionCall true', () => {
renderModal({ isFunctionCall: true })
expect(screen.getByText('appDebug.agent.agentModeType.functionCall')).toBeInTheDocument()
expect(screen.queryByText('tools.builtInPromptTitle')).not.toBeInTheDocument()
})
test('should update iteration via slider and number input', () => {
const { container } = renderModal()
const slider = container.querySelector('.slider') as HTMLInputElement
const numberInput = screen.getByRole('spinbutton')
fireEvent.change(slider, { target: { value: '7' } })
expect(screen.getAllByDisplayValue('7')).toHaveLength(2)
fireEvent.change(numberInput, { target: { value: '2' } })
expect(screen.getAllByDisplayValue('2')).toHaveLength(2)
})
test('should clamp iteration value within min/max range', () => {
renderModal()
const numberInput = screen.getByRole('spinbutton')
fireEvent.change(numberInput, { target: { value: '0' } })
expect(screen.getAllByDisplayValue('1')).toHaveLength(2)
fireEvent.change(numberInput, { target: { value: '999' } })
expect(screen.getAllByDisplayValue(String(MAX_ITERATIONS_NUM))).toHaveLength(2)
})
test('should call onCancel when cancel button clicked', () => {
const { onCancel } = renderModal()
fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
expect(onCancel).toHaveBeenCalled()
})
test('should call onSave with updated payload', async () => {
const { onSave } = renderModal()
const numberInput = screen.getByRole('spinbutton')
fireEvent.change(numberInput, { target: { value: '6' } })
await act(async () => {
fireEvent.click(screen.getByRole('button', { name: 'common.operation.save' }))
})
expect(onSave).toHaveBeenCalledWith(expect.objectContaining({ max_iteration: 6 }))
})
})

View File

@@ -0,0 +1,21 @@
import React from 'react'
import { render, screen } from '@testing-library/react'
import ItemPanel from './item-panel'
describe('AgentSetting/ItemPanel', () => {
test('should render icon, name, and children content', () => {
render(
<ItemPanel
className="custom"
icon={<span>icon</span>}
name="Panel name"
description="More info"
children={<div>child content</div>}
/>,
)
expect(screen.getByText('Panel name')).toBeInTheDocument()
expect(screen.getByText('child content')).toBeInTheDocument()
expect(screen.getByText('icon')).toBeInTheDocument()
})
})

View File

@@ -0,0 +1,466 @@
import type {
PropsWithChildren,
} from 'react'
import React, {
useEffect,
useMemo,
useState,
} from 'react'
import { act, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import AgentTools from './index'
import ConfigContext from '@/context/debug-configuration'
import type { AgentTool } from '@/types/app'
import { CollectionType, type Tool, type ToolParameter } from '@/app/components/tools/types'
import type { ToolWithProvider } from '@/app/components/workflow/types'
import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types'
import type { ModelConfig } from '@/models/debug'
import { ModelModeType } from '@/types/app'
import {
DEFAULT_AGENT_SETTING,
DEFAULT_CHAT_PROMPT_CONFIG,
DEFAULT_COMPLETION_PROMPT_CONFIG,
} from '@/config'
import copy from 'copy-to-clipboard'
import type ToolPickerType from '@/app/components/workflow/block-selector/tool-picker'
import type SettingBuiltInToolType from './setting-built-in-tool'
const formattingDispatcherMock = jest.fn()
jest.mock('@/app/components/app/configuration/debug/hooks', () => ({
useFormattingChangedDispatcher: () => formattingDispatcherMock,
}))
let pluginInstallHandler: ((names: string[]) => void) | null = null
const subscribeMock = jest.fn((event: string, handler: any) => {
if (event === 'plugin:install:success')
pluginInstallHandler = handler
})
jest.mock('@/context/mitt-context', () => ({
useMittContextSelector: (selector: any) => selector({
useSubscribe: subscribeMock,
}),
}))
let builtInTools: ToolWithProvider[] = []
let customTools: ToolWithProvider[] = []
let workflowTools: ToolWithProvider[] = []
let mcpTools: ToolWithProvider[] = []
jest.mock('@/service/use-tools', () => ({
useAllBuiltInTools: () => ({ data: builtInTools }),
useAllCustomTools: () => ({ data: customTools }),
useAllWorkflowTools: () => ({ data: workflowTools }),
useAllMCPTools: () => ({ data: mcpTools }),
}))
type ToolPickerProps = React.ComponentProps<typeof ToolPickerType>
let singleToolSelection: ToolDefaultValue | null = null
let multipleToolSelection: ToolDefaultValue[] = []
const ToolPickerMock = (props: ToolPickerProps) => (
<div data-testid="tool-picker">
<div>{props.trigger}</div>
<button
type="button"
onClick={() => singleToolSelection && props.onSelect(singleToolSelection)}
>
pick-single
</button>
<button
type="button"
onClick={() => props.onSelectMultiple(multipleToolSelection)}
>
pick-multiple
</button>
</div>
)
jest.mock('@/app/components/workflow/block-selector/tool-picker', () => ({
__esModule: true,
default: (props: ToolPickerProps) => <ToolPickerMock {...props} />,
}))
type SettingBuiltInToolProps = React.ComponentProps<typeof SettingBuiltInToolType>
let latestSettingPanelProps: SettingBuiltInToolProps | null = null
let settingPanelSavePayload: Record<string, any> = {}
let settingPanelCredentialId = 'credential-from-panel'
const SettingBuiltInToolMock = (props: SettingBuiltInToolProps) => {
latestSettingPanelProps = props
return (
<div data-testid="setting-built-in-tool">
<span>{props.toolName}</span>
<button type="button" onClick={() => props.onSave?.(settingPanelSavePayload)}>save-from-panel</button>
<button type="button" onClick={() => props.onAuthorizationItemClick?.(settingPanelCredentialId)}>auth-from-panel</button>
<button type="button" onClick={props.onHide}>close-panel</button>
</div>
)
}
jest.mock('./setting-built-in-tool', () => ({
__esModule: true,
default: (props: SettingBuiltInToolProps) => <SettingBuiltInToolMock {...props} />,
}))
jest.mock('copy-to-clipboard')
const copyMock = copy as jest.Mock
const createToolParameter = (overrides?: Partial<ToolParameter>): ToolParameter => ({
name: 'api_key',
label: {
en_US: 'API Key',
zh_Hans: 'API Key',
},
human_description: {
en_US: 'desc',
zh_Hans: 'desc',
},
type: 'string',
form: 'config',
llm_description: '',
required: true,
multiple: false,
default: 'default',
...overrides,
})
const createToolDefinition = (overrides?: Partial<Tool>): Tool => ({
name: 'search',
author: 'tester',
label: {
en_US: 'Search',
zh_Hans: 'Search',
},
description: {
en_US: 'desc',
zh_Hans: 'desc',
},
parameters: [createToolParameter()],
labels: [],
output_schema: {},
...overrides,
})
const createCollection = (overrides?: Partial<ToolWithProvider>): ToolWithProvider => ({
id: overrides?.id || 'provider-1',
name: overrides?.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: overrides?.type || CollectionType.builtIn,
team_credentials: {},
is_team_authorization: true,
allow_delete: true,
labels: [],
tools: overrides?.tools || [createToolDefinition()],
meta: {
version: '1.0.0',
},
...overrides,
})
const createAgentTool = (overrides?: Partial<AgentTool>): AgentTool => ({
provider_id: overrides?.provider_id || 'provider-1',
provider_type: overrides?.provider_type || CollectionType.builtIn,
provider_name: overrides?.provider_name || 'vendor/provider-1',
tool_name: overrides?.tool_name || 'search',
tool_label: overrides?.tool_label || 'Search Tool',
tool_parameters: overrides?.tool_parameters || { api_key: 'key' },
enabled: overrides?.enabled ?? true,
...overrides,
})
const createModelConfig = (tools: AgentTool[]): ModelConfig => ({
provider: 'OPENAI',
model_id: 'gpt-3.5-turbo',
mode: ModelModeType.chat,
configs: {
prompt_template: '',
prompt_variables: [],
},
chat_prompt_config: DEFAULT_CHAT_PROMPT_CONFIG,
completion_prompt_config: DEFAULT_COMPLETION_PROMPT_CONFIG,
opening_statement: '',
more_like_this: null,
suggested_questions: [],
suggested_questions_after_answer: null,
speech_to_text: null,
text_to_speech: null,
file_upload: null,
retriever_resource: null,
sensitive_word_avoidance: null,
annotation_reply: null,
external_data_tools: [],
system_parameters: {
audio_file_size_limit: 0,
file_size_limit: 0,
image_file_size_limit: 0,
video_file_size_limit: 0,
workflow_file_upload_limit: 0,
},
dataSets: [],
agentConfig: {
...DEFAULT_AGENT_SETTING,
tools,
},
})
const renderAgentTools = (initialTools?: AgentTool[]) => {
const tools = initialTools ?? [createAgentTool()]
const modelConfigRef = { current: createModelConfig(tools) }
const Wrapper = ({ children }: PropsWithChildren) => {
const [modelConfig, setModelConfig] = useState<ModelConfig>(modelConfigRef.current)
useEffect(() => {
modelConfigRef.current = modelConfig
}, [modelConfig])
const value = useMemo(() => ({
modelConfig,
setModelConfig,
}), [modelConfig])
return (
<ConfigContext.Provider value={value as any}>
{children}
</ConfigContext.Provider>
)
}
const renderResult = render(
<Wrapper>
<AgentTools />
</Wrapper>,
)
return {
...renderResult,
getModelConfig: () => modelConfigRef.current,
}
}
const hoverInfoIcon = async (rowIndex = 0) => {
const rows = document.querySelectorAll('.group')
const infoTrigger = rows.item(rowIndex)?.querySelector('[data-testid="tool-info-tooltip"]')
if (!infoTrigger)
throw new Error('Info trigger not found')
await userEvent.hover(infoTrigger as HTMLElement)
}
describe('AgentTools', () => {
beforeEach(() => {
jest.clearAllMocks()
builtInTools = [
createCollection(),
createCollection({
id: 'provider-2',
name: 'vendor/provider-2',
tools: [createToolDefinition({
name: 'translate',
label: {
en_US: 'Translate',
zh_Hans: 'Translate',
},
})],
}),
createCollection({
id: 'provider-3',
name: 'vendor/provider-3',
tools: [createToolDefinition({
name: 'summarize',
label: {
en_US: 'Summary',
zh_Hans: 'Summary',
},
})],
}),
]
customTools = []
workflowTools = []
mcpTools = []
singleToolSelection = {
provider_id: 'provider-3',
provider_type: CollectionType.builtIn,
provider_name: 'vendor/provider-3',
tool_name: 'summarize',
tool_label: 'Summary Tool',
tool_description: 'desc',
title: 'Summary Tool',
is_team_authorization: true,
params: { api_key: 'picker-value' },
paramSchemas: [],
output_schema: {},
}
multipleToolSelection = [
{
provider_id: 'provider-2',
provider_type: CollectionType.builtIn,
provider_name: 'vendor/provider-2',
tool_name: 'translate',
tool_label: 'Translate Tool',
tool_description: 'desc',
title: 'Translate Tool',
is_team_authorization: true,
params: { api_key: 'multi-a' },
paramSchemas: [],
output_schema: {},
},
{
provider_id: 'provider-3',
provider_type: CollectionType.builtIn,
provider_name: 'vendor/provider-3',
tool_name: 'summarize',
tool_label: 'Summary Tool',
tool_description: 'desc',
title: 'Summary Tool',
is_team_authorization: true,
params: { api_key: 'multi-b' },
paramSchemas: [],
output_schema: {},
},
]
latestSettingPanelProps = null
settingPanelSavePayload = {}
settingPanelCredentialId = 'credential-from-panel'
pluginInstallHandler = null
})
test('should show enabled count and provider information', () => {
renderAgentTools([
createAgentTool(),
createAgentTool({
provider_id: 'provider-2',
provider_name: 'vendor/provider-2',
tool_name: 'translate',
tool_label: 'Translate Tool',
enabled: false,
}),
])
const enabledText = screen.getByText(content => content.includes('appDebug.agent.tools.enabled'))
expect(enabledText).toHaveTextContent('1/2')
expect(screen.getByText('provider-1')).toBeInTheDocument()
expect(screen.getByText('Translate Tool')).toBeInTheDocument()
})
test('should copy tool name from tooltip action', async () => {
renderAgentTools()
await hoverInfoIcon()
const copyButton = await screen.findByText('tools.copyToolName')
await userEvent.click(copyButton)
expect(copyMock).toHaveBeenCalledWith('search')
})
test('should toggle tool enabled state via switch', async () => {
const { getModelConfig } = renderAgentTools()
const switchButton = screen.getByRole('switch')
await userEvent.click(switchButton)
await waitFor(() => {
const tools = getModelConfig().agentConfig.tools as Array<{ tool_name?: string; enabled?: boolean }>
const toggledTool = tools.find(tool => tool.tool_name === 'search')
expect(toggledTool?.enabled).toBe(false)
})
expect(formattingDispatcherMock).toHaveBeenCalled()
})
test('should remove tool when delete action is clicked', async () => {
const { getModelConfig } = renderAgentTools()
const deleteButton = screen.getByTestId('delete-removed-tool')
if (!deleteButton)
throw new Error('Delete button not found')
await userEvent.click(deleteButton)
await waitFor(() => {
expect(getModelConfig().agentConfig.tools).toHaveLength(0)
})
expect(formattingDispatcherMock).toHaveBeenCalled()
})
test('should add a tool when ToolPicker selects one', async () => {
const { getModelConfig } = renderAgentTools([])
const addSingleButton = screen.getByRole('button', { name: 'pick-single' })
await userEvent.click(addSingleButton)
await waitFor(() => {
expect(screen.getByText('Summary Tool')).toBeInTheDocument()
})
expect(getModelConfig().agentConfig.tools).toHaveLength(1)
})
test('should append multiple selected tools at once', async () => {
const { getModelConfig } = renderAgentTools([])
await userEvent.click(screen.getByRole('button', { name: 'pick-multiple' }))
await waitFor(() => {
expect(screen.getByText('Translate Tool')).toBeInTheDocument()
expect(screen.getAllByText('Summary Tool')).toHaveLength(1)
})
expect(getModelConfig().agentConfig.tools).toHaveLength(2)
})
test('should open settings panel for not authorized tool', async () => {
renderAgentTools([
createAgentTool({
notAuthor: true,
}),
])
const notAuthorizedButton = screen.getByRole('button', { name: /tools.notAuthorized/ })
await userEvent.click(notAuthorizedButton)
expect(screen.getByTestId('setting-built-in-tool')).toBeInTheDocument()
expect(latestSettingPanelProps?.toolName).toBe('search')
})
test('should persist tool parameters when SettingBuiltInTool saves values', async () => {
const { getModelConfig } = renderAgentTools([
createAgentTool({
notAuthor: true,
}),
])
await userEvent.click(screen.getByRole('button', { name: /tools.notAuthorized/ }))
settingPanelSavePayload = { api_key: 'updated' }
await userEvent.click(screen.getByRole('button', { name: 'save-from-panel' }))
await waitFor(() => {
expect((getModelConfig().agentConfig.tools[0] as { tool_parameters: Record<string, any> }).tool_parameters).toEqual({ api_key: 'updated' })
})
})
test('should update credential id when authorization selection changes', async () => {
const { getModelConfig } = renderAgentTools([
createAgentTool({
notAuthor: true,
}),
])
await userEvent.click(screen.getByRole('button', { name: /tools.notAuthorized/ }))
settingPanelCredentialId = 'credential-123'
await userEvent.click(screen.getByRole('button', { name: 'auth-from-panel' }))
await waitFor(() => {
expect((getModelConfig().agentConfig.tools[0] as { credential_id: string }).credential_id).toBe('credential-123')
})
expect(formattingDispatcherMock).toHaveBeenCalled()
})
test('should reinstate deleted tools after plugin install success event', async () => {
const { getModelConfig } = renderAgentTools([
createAgentTool({
provider_id: 'provider-1',
provider_name: 'vendor/provider-1',
tool_name: 'search',
tool_label: 'Search Tool',
isDeleted: true,
}),
])
if (!pluginInstallHandler)
throw new Error('Plugin handler not registered')
await act(async () => {
pluginInstallHandler?.(['provider-1'])
})
await waitFor(() => {
expect((getModelConfig().agentConfig.tools[0] as { isDeleted: boolean }).isDeleted).toBe(false)
})
})
})

View File

@@ -217,7 +217,7 @@ const AgentTools: FC = () => {
}
>
<div className='h-4 w-4'>
<div className='ml-0.5 hidden group-hover:inline-block'>
<div className='ml-0.5 hidden group-hover:inline-block' data-testid='tool-info-tooltip'>
<RiInformation2Line className='h-4 w-4 text-text-tertiary' />
</div>
</div>
@@ -277,6 +277,7 @@ const AgentTools: FC = () => {
}}
onMouseOver={() => setIsDeleting(index)}
onMouseLeave={() => setIsDeleting(-1)}
data-testid='delete-removed-tool'
>
<RiDeleteBinLine className='h-4 w-4' />
</div>

View File

@@ -0,0 +1,248 @@
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')
})
})
})