chore: tests for configuration (#29870)

This commit is contained in:
Joel
2025-12-18 17:18:24 +08:00
committed by GitHub
parent 5067e4f255
commit 5638dcc7ad
5 changed files with 823 additions and 0 deletions

View File

@@ -0,0 +1,227 @@
import React from 'react'
import { fireEvent, render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import ConfigVision from './index'
import ParamConfig from './param-config'
import ParamConfigContent from './param-config-content'
import type { FeatureStoreState } from '@/app/components/base/features/store'
import type { FileUpload } from '@/app/components/base/features/types'
import { Resolution, TransferMethod } from '@/types/app'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
const mockUseContext = jest.fn()
jest.mock('use-context-selector', () => {
const actual = jest.requireActual('use-context-selector')
return {
...actual,
useContext: (context: unknown) => mockUseContext(context),
}
})
const mockUseFeatures = jest.fn()
const mockUseFeaturesStore = jest.fn()
jest.mock('@/app/components/base/features/hooks', () => ({
useFeatures: (selector: (state: FeatureStoreState) => any) => mockUseFeatures(selector),
useFeaturesStore: () => mockUseFeaturesStore(),
}))
const defaultFile: FileUpload = {
enabled: false,
allowed_file_types: [],
allowed_file_upload_methods: [TransferMethod.local_file, TransferMethod.remote_url],
number_limits: 3,
image: {
enabled: false,
detail: Resolution.low,
number_limits: 3,
transfer_methods: [TransferMethod.local_file, TransferMethod.remote_url],
},
}
let featureStoreState: FeatureStoreState
let setFeaturesMock: jest.Mock
const setupFeatureStore = (fileOverrides: Partial<FileUpload> = {}) => {
const mergedFile: FileUpload = {
...defaultFile,
...fileOverrides,
image: {
...defaultFile.image,
...fileOverrides.image,
},
}
featureStoreState = {
features: {
file: mergedFile,
},
setFeatures: jest.fn(),
showFeaturesModal: false,
setShowFeaturesModal: jest.fn(),
}
setFeaturesMock = featureStoreState.setFeatures as jest.Mock
mockUseFeaturesStore.mockReturnValue({
getState: () => featureStoreState,
})
mockUseFeatures.mockImplementation(selector => selector(featureStoreState))
}
const getLatestFileConfig = () => {
expect(setFeaturesMock).toHaveBeenCalled()
const latestFeatures = setFeaturesMock.mock.calls[setFeaturesMock.mock.calls.length - 1][0] as { file: FileUpload }
return latestFeatures.file
}
beforeEach(() => {
jest.clearAllMocks()
mockUseContext.mockReturnValue({
isShowVisionConfig: true,
isAllowVideoUpload: false,
})
setupFeatureStore()
})
// ConfigVision handles toggling file upload types + visibility rules.
describe('ConfigVision', () => {
it('should not render when vision configuration is hidden', () => {
mockUseContext.mockReturnValue({
isShowVisionConfig: false,
isAllowVideoUpload: false,
})
render(<ConfigVision />)
expect(screen.queryByText('appDebug.vision.name')).not.toBeInTheDocument()
})
it('should show the toggle and parameter controls when visible', () => {
render(<ConfigVision />)
expect(screen.getByText('appDebug.vision.name')).toBeInTheDocument()
expect(screen.getByRole('switch')).toHaveAttribute('aria-checked', 'false')
})
it('should enable both image and video uploads when toggled on with video support', async () => {
const user = userEvent.setup()
mockUseContext.mockReturnValue({
isShowVisionConfig: true,
isAllowVideoUpload: true,
})
setupFeatureStore({
allowed_file_types: [],
})
render(<ConfigVision />)
await user.click(screen.getByRole('switch'))
const updatedFile = getLatestFileConfig()
expect(updatedFile.allowed_file_types).toEqual([SupportUploadFileTypes.image, SupportUploadFileTypes.video])
expect(updatedFile.image?.enabled).toBe(true)
expect(updatedFile.enabled).toBe(true)
})
it('should disable image and video uploads when toggled off and no other types remain', async () => {
const user = userEvent.setup()
mockUseContext.mockReturnValue({
isShowVisionConfig: true,
isAllowVideoUpload: true,
})
setupFeatureStore({
allowed_file_types: [SupportUploadFileTypes.image, SupportUploadFileTypes.video],
enabled: true,
image: {
enabled: true,
},
})
render(<ConfigVision />)
await user.click(screen.getByRole('switch'))
const updatedFile = getLatestFileConfig()
expect(updatedFile.allowed_file_types).toEqual([])
expect(updatedFile.enabled).toBe(false)
expect(updatedFile.image?.enabled).toBe(false)
})
it('should keep file uploads enabled when other file types remain after disabling vision', async () => {
const user = userEvent.setup()
mockUseContext.mockReturnValue({
isShowVisionConfig: true,
isAllowVideoUpload: false,
})
setupFeatureStore({
allowed_file_types: [SupportUploadFileTypes.image, SupportUploadFileTypes.document],
enabled: true,
image: { enabled: true },
})
render(<ConfigVision />)
await user.click(screen.getByRole('switch'))
const updatedFile = getLatestFileConfig()
expect(updatedFile.allowed_file_types).toEqual([SupportUploadFileTypes.document])
expect(updatedFile.enabled).toBe(true)
expect(updatedFile.image?.enabled).toBe(false)
})
})
// ParamConfig exposes ParamConfigContent via an inline trigger.
describe('ParamConfig', () => {
it('should toggle parameter panel when clicking the settings button', async () => {
setupFeatureStore()
const user = userEvent.setup()
render(<ParamConfig />)
expect(screen.queryByText('appDebug.vision.visionSettings.title')).not.toBeInTheDocument()
await user.click(screen.getByRole('button', { name: 'appDebug.voice.settings' }))
expect(await screen.findByText('appDebug.vision.visionSettings.title')).toBeInTheDocument()
})
})
// ParamConfigContent manages resolution, upload source, and count limits.
describe('ParamConfigContent', () => {
it('should set resolution to high when the corresponding option is selected', async () => {
const user = userEvent.setup()
setupFeatureStore({
image: { detail: Resolution.low },
})
render(<ParamConfigContent />)
await user.click(screen.getByText('appDebug.vision.visionSettings.high'))
const updatedFile = getLatestFileConfig()
expect(updatedFile.image?.detail).toBe(Resolution.high)
})
it('should switch upload method to local only', async () => {
const user = userEvent.setup()
setupFeatureStore({
allowed_file_upload_methods: [TransferMethod.local_file, TransferMethod.remote_url],
})
render(<ParamConfigContent />)
await user.click(screen.getByText('appDebug.vision.visionSettings.localUpload'))
const updatedFile = getLatestFileConfig()
expect(updatedFile.allowed_file_upload_methods).toEqual([TransferMethod.local_file])
expect(updatedFile.image?.transfer_methods).toEqual([TransferMethod.local_file])
})
it('should update upload limit value when input changes', async () => {
setupFeatureStore({
number_limits: 2,
})
render(<ParamConfigContent />)
const input = screen.getByRole('spinbutton') as HTMLInputElement
fireEvent.change(input, { target: { value: '4' } })
const updatedFile = getLatestFileConfig()
expect(updatedFile.number_limits).toBe(4)
expect(updatedFile.image?.number_limits).toBe(4)
})
})

View File

@@ -0,0 +1,100 @@
import React from 'react'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import AgentSettingButton from './agent-setting-button'
import type { AgentConfig } from '@/models/debug'
import { AgentStrategy } from '@/types/app'
jest.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}))
let latestAgentSettingProps: any
jest.mock('./agent/agent-setting', () => ({
__esModule: true,
default: (props: any) => {
latestAgentSettingProps = props
return (
<div data-testid="agent-setting">
<button onClick={() => props.onSave({ ...props.payload, max_iteration: 9 })}>
save-agent
</button>
<button onClick={props.onCancel}>
cancel-agent
</button>
</div>
)
},
}))
const createAgentConfig = (overrides: Partial<AgentConfig> = {}): AgentConfig => ({
enabled: true,
strategy: AgentStrategy.react,
max_iteration: 3,
tools: [],
...overrides,
})
const setup = (overrides: Partial<React.ComponentProps<typeof AgentSettingButton>> = {}) => {
const props: React.ComponentProps<typeof AgentSettingButton> = {
isFunctionCall: false,
isChatModel: true,
onAgentSettingChange: jest.fn(),
agentConfig: createAgentConfig(),
...overrides,
}
const user = userEvent.setup()
render(<AgentSettingButton {...props} />)
return { props, user }
}
beforeEach(() => {
jest.clearAllMocks()
latestAgentSettingProps = undefined
})
describe('AgentSettingButton', () => {
it('should render button label from translation key', () => {
setup()
expect(screen.getByRole('button', { name: 'appDebug.agent.setting.name' })).toBeInTheDocument()
})
it('should open AgentSetting with the provided configuration when clicked', async () => {
const { user, props } = setup({ isFunctionCall: true, isChatModel: false })
await user.click(screen.getByRole('button', { name: 'appDebug.agent.setting.name' }))
expect(screen.getByTestId('agent-setting')).toBeInTheDocument()
expect(latestAgentSettingProps.isFunctionCall).toBe(true)
expect(latestAgentSettingProps.isChatModel).toBe(false)
expect(latestAgentSettingProps.payload).toEqual(props.agentConfig)
})
it('should call onAgentSettingChange and close when AgentSetting saves', async () => {
const { user, props } = setup()
await user.click(screen.getByRole('button', { name: 'appDebug.agent.setting.name' }))
await user.click(screen.getByText('save-agent'))
expect(props.onAgentSettingChange).toHaveBeenCalledTimes(1)
expect(props.onAgentSettingChange).toHaveBeenCalledWith({
...props.agentConfig,
max_iteration: 9,
})
expect(screen.queryByTestId('agent-setting')).not.toBeInTheDocument()
})
it('should close AgentSetting without saving when cancel is triggered', async () => {
const { user, props } = setup()
await user.click(screen.getByRole('button', { name: 'appDebug.agent.setting.name' }))
await user.click(screen.getByText('cancel-agent'))
expect(props.onAgentSettingChange).not.toHaveBeenCalled()
expect(screen.queryByTestId('agent-setting')).not.toBeInTheDocument()
})
})

View File

@@ -0,0 +1,123 @@
import React from 'react'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import ConfigAudio from './config-audio'
import type { FeatureStoreState } from '@/app/components/base/features/store'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
const mockUseContext = jest.fn()
jest.mock('use-context-selector', () => {
const actual = jest.requireActual('use-context-selector')
return {
...actual,
useContext: (context: unknown) => mockUseContext(context),
}
})
jest.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}))
const mockUseFeatures = jest.fn()
const mockUseFeaturesStore = jest.fn()
jest.mock('@/app/components/base/features/hooks', () => ({
useFeatures: (selector: (state: FeatureStoreState) => any) => mockUseFeatures(selector),
useFeaturesStore: () => mockUseFeaturesStore(),
}))
type SetupOptions = {
isVisible?: boolean
allowedTypes?: SupportUploadFileTypes[]
}
let mockFeatureStoreState: FeatureStoreState
let mockSetFeatures: jest.Mock
const mockStore = {
getState: jest.fn<FeatureStoreState, []>(() => mockFeatureStoreState),
}
const setupFeatureStore = (allowedTypes: SupportUploadFileTypes[] = []) => {
mockSetFeatures = jest.fn()
mockFeatureStoreState = {
features: {
file: {
allowed_file_types: allowedTypes,
enabled: allowedTypes.length > 0,
},
},
setFeatures: mockSetFeatures,
showFeaturesModal: false,
setShowFeaturesModal: jest.fn(),
}
mockStore.getState.mockImplementation(() => mockFeatureStoreState)
mockUseFeaturesStore.mockReturnValue(mockStore)
mockUseFeatures.mockImplementation(selector => selector(mockFeatureStoreState))
}
const renderConfigAudio = (options: SetupOptions = {}) => {
const {
isVisible = true,
allowedTypes = [],
} = options
setupFeatureStore(allowedTypes)
mockUseContext.mockReturnValue({
isShowAudioConfig: isVisible,
})
const user = userEvent.setup()
render(<ConfigAudio />)
return {
user,
setFeatures: mockSetFeatures,
}
}
beforeEach(() => {
jest.clearAllMocks()
})
describe('ConfigAudio', () => {
it('should not render when the audio configuration is hidden', () => {
renderConfigAudio({ isVisible: false })
expect(screen.queryByText('appDebug.feature.audioUpload.title')).not.toBeInTheDocument()
})
it('should display the audio toggle state based on feature store data', () => {
renderConfigAudio({ allowedTypes: [SupportUploadFileTypes.audio] })
expect(screen.getByText('appDebug.feature.audioUpload.title')).toBeInTheDocument()
expect(screen.getByRole('switch')).toHaveAttribute('aria-checked', 'true')
})
it('should enable audio uploads when toggled on', async () => {
const { user, setFeatures } = renderConfigAudio()
const toggle = screen.getByRole('switch')
expect(toggle).toHaveAttribute('aria-checked', 'false')
await user.click(toggle)
expect(setFeatures).toHaveBeenCalledWith(expect.objectContaining({
file: expect.objectContaining({
allowed_file_types: [SupportUploadFileTypes.audio],
enabled: true,
}),
}))
})
it('should disable audio uploads and turn off file feature when last type is removed', async () => {
const { user, setFeatures } = renderConfigAudio({ allowedTypes: [SupportUploadFileTypes.audio] })
const toggle = screen.getByRole('switch')
expect(toggle).toHaveAttribute('aria-checked', 'true')
await user.click(toggle)
expect(setFeatures).toHaveBeenCalledWith(expect.objectContaining({
file: expect.objectContaining({
allowed_file_types: [],
enabled: false,
}),
}))
})
})

View File

@@ -0,0 +1,119 @@
import React from 'react'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import ConfigDocument from './config-document'
import type { FeatureStoreState } from '@/app/components/base/features/store'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
const mockUseContext = jest.fn()
jest.mock('use-context-selector', () => {
const actual = jest.requireActual('use-context-selector')
return {
...actual,
useContext: (context: unknown) => mockUseContext(context),
}
})
const mockUseFeatures = jest.fn()
const mockUseFeaturesStore = jest.fn()
jest.mock('@/app/components/base/features/hooks', () => ({
useFeatures: (selector: (state: FeatureStoreState) => any) => mockUseFeatures(selector),
useFeaturesStore: () => mockUseFeaturesStore(),
}))
type SetupOptions = {
isVisible?: boolean
allowedTypes?: SupportUploadFileTypes[]
}
let mockFeatureStoreState: FeatureStoreState
let mockSetFeatures: jest.Mock
const mockStore = {
getState: jest.fn<FeatureStoreState, []>(() => mockFeatureStoreState),
}
const setupFeatureStore = (allowedTypes: SupportUploadFileTypes[] = []) => {
mockSetFeatures = jest.fn()
mockFeatureStoreState = {
features: {
file: {
allowed_file_types: allowedTypes,
enabled: allowedTypes.length > 0,
},
},
setFeatures: mockSetFeatures,
showFeaturesModal: false,
setShowFeaturesModal: jest.fn(),
}
mockStore.getState.mockImplementation(() => mockFeatureStoreState)
mockUseFeaturesStore.mockReturnValue(mockStore)
mockUseFeatures.mockImplementation(selector => selector(mockFeatureStoreState))
}
const renderConfigDocument = (options: SetupOptions = {}) => {
const {
isVisible = true,
allowedTypes = [],
} = options
setupFeatureStore(allowedTypes)
mockUseContext.mockReturnValue({
isShowDocumentConfig: isVisible,
})
const user = userEvent.setup()
render(<ConfigDocument />)
return {
user,
setFeatures: mockSetFeatures,
}
}
beforeEach(() => {
jest.clearAllMocks()
})
describe('ConfigDocument', () => {
it('should not render when the document configuration is hidden', () => {
renderConfigDocument({ isVisible: false })
expect(screen.queryByText('appDebug.feature.documentUpload.title')).not.toBeInTheDocument()
})
it('should show document toggle badge when configuration is visible', () => {
renderConfigDocument({ allowedTypes: [SupportUploadFileTypes.document] })
expect(screen.getByText('appDebug.feature.documentUpload.title')).toBeInTheDocument()
expect(screen.getByRole('switch')).toHaveAttribute('aria-checked', 'true')
})
it('should add document type to allowed list when toggled on', async () => {
const { user, setFeatures } = renderConfigDocument({ allowedTypes: [SupportUploadFileTypes.audio] })
const toggle = screen.getByRole('switch')
expect(toggle).toHaveAttribute('aria-checked', 'false')
await user.click(toggle)
expect(setFeatures).toHaveBeenCalledWith(expect.objectContaining({
file: expect.objectContaining({
allowed_file_types: [SupportUploadFileTypes.audio, SupportUploadFileTypes.document],
enabled: true,
}),
}))
})
it('should remove document type but keep file feature enabled when other types remain', async () => {
const { user, setFeatures } = renderConfigDocument({
allowedTypes: [SupportUploadFileTypes.document, SupportUploadFileTypes.audio],
})
const toggle = screen.getByRole('switch')
expect(toggle).toHaveAttribute('aria-checked', 'true')
await user.click(toggle)
expect(setFeatures).toHaveBeenCalledWith(expect.objectContaining({
file: expect.objectContaining({
allowed_file_types: [SupportUploadFileTypes.audio],
enabled: true,
}),
}))
})
})

View File

@@ -0,0 +1,254 @@
import React from 'react'
import { render, screen } from '@testing-library/react'
import Config from './index'
import type { ModelConfig, PromptVariable } from '@/models/debug'
import * as useContextSelector from 'use-context-selector'
import type { ToolItem } from '@/types/app'
import { AgentStrategy, AppModeEnum, ModelModeType } from '@/types/app'
jest.mock('use-context-selector', () => {
const actual = jest.requireActual('use-context-selector')
return {
...actual,
useContext: jest.fn(),
}
})
const mockFormattingDispatcher = jest.fn()
jest.mock('../debug/hooks', () => ({
__esModule: true,
useFormattingChangedDispatcher: () => mockFormattingDispatcher,
}))
let latestConfigPromptProps: any
jest.mock('@/app/components/app/configuration/config-prompt', () => ({
__esModule: true,
default: (props: any) => {
latestConfigPromptProps = props
return <div data-testid="config-prompt" />
},
}))
let latestConfigVarProps: any
jest.mock('@/app/components/app/configuration/config-var', () => ({
__esModule: true,
default: (props: any) => {
latestConfigVarProps = props
return <div data-testid="config-var" />
},
}))
jest.mock('../dataset-config', () => ({
__esModule: true,
default: () => <div data-testid="dataset-config" />,
}))
jest.mock('./agent/agent-tools', () => ({
__esModule: true,
default: () => <div data-testid="agent-tools" />,
}))
jest.mock('../config-vision', () => ({
__esModule: true,
default: () => <div data-testid="config-vision" />,
}))
jest.mock('./config-document', () => ({
__esModule: true,
default: () => <div data-testid="config-document" />,
}))
jest.mock('./config-audio', () => ({
__esModule: true,
default: () => <div data-testid="config-audio" />,
}))
let latestHistoryPanelProps: any
jest.mock('../config-prompt/conversation-history/history-panel', () => ({
__esModule: true,
default: (props: any) => {
latestHistoryPanelProps = props
return <div data-testid="history-panel" />
},
}))
type MockContext = {
mode: AppModeEnum
isAdvancedMode: boolean
modelModeType: ModelModeType
isAgent: boolean
hasSetBlockStatus: {
context: boolean
history: boolean
query: boolean
}
showHistoryModal: jest.Mock
modelConfig: ModelConfig
setModelConfig: jest.Mock
setPrevPromptConfig: jest.Mock
}
const createPromptVariable = (overrides: Partial<PromptVariable> = {}): PromptVariable => ({
key: 'variable',
name: 'Variable',
type: 'string',
...overrides,
})
const createModelConfig = (overrides: Partial<ModelConfig> = {}): ModelConfig => ({
provider: 'openai',
model_id: 'gpt-4',
mode: ModelModeType.chat,
configs: {
prompt_template: 'Hello {{variable}}',
prompt_variables: [createPromptVariable({ key: 'existing' })],
},
chat_prompt_config: null,
completion_prompt_config: null,
opening_statement: null,
more_like_this: null,
suggested_questions: null,
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: null,
system_parameters: {
audio_file_size_limit: 1,
file_size_limit: 1,
image_file_size_limit: 1,
video_file_size_limit: 1,
workflow_file_upload_limit: 1,
},
dataSets: [],
agentConfig: {
enabled: false,
strategy: AgentStrategy.react,
max_iteration: 1,
tools: [] as ToolItem[],
},
...overrides,
})
const createContextValue = (overrides: Partial<MockContext> = {}): MockContext => ({
mode: AppModeEnum.CHAT,
isAdvancedMode: false,
modelModeType: ModelModeType.chat,
isAgent: false,
hasSetBlockStatus: {
context: false,
history: true,
query: false,
},
showHistoryModal: jest.fn(),
modelConfig: createModelConfig(),
setModelConfig: jest.fn(),
setPrevPromptConfig: jest.fn(),
...overrides,
})
const mockUseContext = useContextSelector.useContext as jest.Mock
const renderConfig = (contextOverrides: Partial<MockContext> = {}) => {
const contextValue = createContextValue(contextOverrides)
mockUseContext.mockReturnValue(contextValue)
return {
contextValue,
...render(<Config />),
}
}
beforeEach(() => {
jest.clearAllMocks()
latestConfigPromptProps = undefined
latestConfigVarProps = undefined
latestHistoryPanelProps = undefined
})
// Rendering scenarios ensure the layout toggles agent/history specific sections correctly.
describe('Config - Rendering', () => {
it('should render baseline sections without agent specific panels', () => {
renderConfig()
expect(screen.getByTestId('config-prompt')).toBeInTheDocument()
expect(screen.getByTestId('config-var')).toBeInTheDocument()
expect(screen.getByTestId('dataset-config')).toBeInTheDocument()
expect(screen.getByTestId('config-vision')).toBeInTheDocument()
expect(screen.getByTestId('config-document')).toBeInTheDocument()
expect(screen.getByTestId('config-audio')).toBeInTheDocument()
expect(screen.queryByTestId('agent-tools')).not.toBeInTheDocument()
expect(screen.queryByTestId('history-panel')).not.toBeInTheDocument()
})
it('should show AgentTools when app runs in agent mode', () => {
renderConfig({ isAgent: true })
expect(screen.getByTestId('agent-tools')).toBeInTheDocument()
})
it('should display HistoryPanel only when advanced chat completion values apply', () => {
const showHistoryModal = jest.fn()
renderConfig({
isAdvancedMode: true,
mode: AppModeEnum.ADVANCED_CHAT,
modelModeType: ModelModeType.completion,
hasSetBlockStatus: {
context: false,
history: false,
query: false,
},
showHistoryModal,
})
expect(screen.getByTestId('history-panel')).toBeInTheDocument()
expect(latestHistoryPanelProps.showWarning).toBe(true)
expect(latestHistoryPanelProps.onShowEditModal).toBe(showHistoryModal)
})
})
// Prompt handling scenarios validate integration between Config and prompt children.
describe('Config - Prompt Handling', () => {
it('should update prompt template and dispatch formatting event when text changes', () => {
const { contextValue } = renderConfig()
const previousVariables = contextValue.modelConfig.configs.prompt_variables
const additions = [createPromptVariable({ key: 'new', name: 'New' })]
latestConfigPromptProps.onChange('Updated template', additions)
expect(contextValue.setPrevPromptConfig).toHaveBeenCalledWith(contextValue.modelConfig.configs)
expect(contextValue.setModelConfig).toHaveBeenCalledWith(expect.objectContaining({
configs: expect.objectContaining({
prompt_template: 'Updated template',
prompt_variables: [...previousVariables, ...additions],
}),
}))
expect(mockFormattingDispatcher).toHaveBeenCalledTimes(1)
})
it('should skip formatting dispatcher when template remains identical', () => {
const { contextValue } = renderConfig()
const unchangedTemplate = contextValue.modelConfig.configs.prompt_template
latestConfigPromptProps.onChange(unchangedTemplate, [createPromptVariable({ key: 'added' })])
expect(contextValue.setPrevPromptConfig).toHaveBeenCalledWith(contextValue.modelConfig.configs)
expect(mockFormattingDispatcher).not.toHaveBeenCalled()
})
it('should replace prompt variables when ConfigVar reports updates', () => {
const { contextValue } = renderConfig()
const replacementVariables = [createPromptVariable({ key: 'replacement' })]
latestConfigVarProps.onPromptVariablesChange(replacementVariables)
expect(contextValue.setPrevPromptConfig).toHaveBeenCalledWith(contextValue.modelConfig.configs)
expect(contextValue.setModelConfig).toHaveBeenCalledWith(expect.objectContaining({
configs: expect.objectContaining({
prompt_variables: replacementVariables,
}),
}))
})
})