mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 01:48:04 +08:00
chore: add some tests case code (#29927)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Coding On Star <447357187@qq.com>
This commit is contained in:
241
web/app/components/share/text-generation/run-once/index.spec.tsx
Normal file
241
web/app/components/share/text-generation/run-once/index.spec.tsx
Normal file
@ -0,0 +1,241 @@
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import RunOnce from './index'
|
||||
import type { PromptConfig, PromptVariable } from '@/models/debug'
|
||||
import type { SiteInfo } from '@/models/share'
|
||||
import type { VisionSettings } from '@/types/app'
|
||||
import { Resolution, TransferMethod } from '@/types/app'
|
||||
|
||||
jest.mock('@/hooks/use-breakpoints', () => {
|
||||
const MediaType = {
|
||||
pc: 'pc',
|
||||
pad: 'pad',
|
||||
mobile: 'mobile',
|
||||
}
|
||||
const mockUseBreakpoints = jest.fn(() => MediaType.pc)
|
||||
return {
|
||||
__esModule: true,
|
||||
default: mockUseBreakpoints,
|
||||
MediaType,
|
||||
}
|
||||
})
|
||||
|
||||
jest.mock('@/app/components/workflow/nodes/_base/components/editor/code-editor', () => ({
|
||||
__esModule: true,
|
||||
default: ({ value, onChange }: { value?: string; onChange?: (val: string) => void }) => (
|
||||
<textarea data-testid="code-editor-mock" value={value} onChange={e => onChange?.(e.target.value)} />
|
||||
),
|
||||
}))
|
||||
|
||||
jest.mock('@/app/components/base/image-uploader/text-generation-image-uploader', () => {
|
||||
function TextGenerationImageUploaderMock({ onFilesChange }: { onFilesChange: (files: any[]) => void }) {
|
||||
useEffect(() => {
|
||||
onFilesChange([])
|
||||
}, [onFilesChange])
|
||||
return <div data-testid="vision-uploader-mock" />
|
||||
}
|
||||
return {
|
||||
__esModule: true,
|
||||
default: TextGenerationImageUploaderMock,
|
||||
}
|
||||
})
|
||||
|
||||
const createPromptVariable = (overrides: Partial<PromptVariable>): PromptVariable => ({
|
||||
key: 'input',
|
||||
name: 'Input',
|
||||
type: 'string',
|
||||
required: true,
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const basePromptConfig: PromptConfig = {
|
||||
prompt_template: 'template',
|
||||
prompt_variables: [
|
||||
createPromptVariable({
|
||||
key: 'textInput',
|
||||
name: 'Text Input',
|
||||
type: 'string',
|
||||
default: 'default text',
|
||||
}),
|
||||
createPromptVariable({
|
||||
key: 'paragraphInput',
|
||||
name: 'Paragraph Input',
|
||||
type: 'paragraph',
|
||||
default: 'paragraph default',
|
||||
}),
|
||||
createPromptVariable({
|
||||
key: 'numberInput',
|
||||
name: 'Number Input',
|
||||
type: 'number',
|
||||
default: 42,
|
||||
}),
|
||||
createPromptVariable({
|
||||
key: 'checkboxInput',
|
||||
name: 'Checkbox Input',
|
||||
type: 'checkbox',
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
||||
const baseVisionConfig: VisionSettings = {
|
||||
enabled: true,
|
||||
number_limits: 2,
|
||||
detail: Resolution.low,
|
||||
transfer_methods: [TransferMethod.local_file],
|
||||
image_file_size_limit: 5,
|
||||
}
|
||||
|
||||
const siteInfo: SiteInfo = {
|
||||
title: 'Share',
|
||||
}
|
||||
|
||||
const setup = (overrides: {
|
||||
promptConfig?: PromptConfig
|
||||
visionConfig?: VisionSettings
|
||||
runControl?: React.ComponentProps<typeof RunOnce>['runControl']
|
||||
} = {}) => {
|
||||
const onInputsChange = jest.fn()
|
||||
const onSend = jest.fn()
|
||||
const onVisionFilesChange = jest.fn()
|
||||
let inputsRefCapture: React.MutableRefObject<Record<string, any>> | null = null
|
||||
|
||||
const Wrapper = () => {
|
||||
const [inputs, setInputs] = useState<Record<string, any>>({})
|
||||
const inputsRef = useRef<Record<string, any>>({})
|
||||
inputsRefCapture = inputsRef
|
||||
return (
|
||||
<RunOnce
|
||||
siteInfo={siteInfo}
|
||||
promptConfig={overrides.promptConfig || basePromptConfig}
|
||||
inputs={inputs}
|
||||
inputsRef={inputsRef}
|
||||
onInputsChange={(updated) => {
|
||||
inputsRef.current = updated
|
||||
setInputs(updated)
|
||||
onInputsChange(updated)
|
||||
}}
|
||||
onSend={onSend}
|
||||
visionConfig={overrides.visionConfig || baseVisionConfig}
|
||||
onVisionFilesChange={onVisionFilesChange}
|
||||
runControl={overrides.runControl ?? null}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const utils = render(<Wrapper />)
|
||||
return {
|
||||
...utils,
|
||||
onInputsChange,
|
||||
onSend,
|
||||
onVisionFilesChange,
|
||||
getInputsRef: () => inputsRefCapture,
|
||||
}
|
||||
}
|
||||
|
||||
describe('RunOnce', () => {
|
||||
it('should initialize inputs using prompt defaults', async () => {
|
||||
const { onInputsChange, onVisionFilesChange } = setup()
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onInputsChange).toHaveBeenCalledWith({
|
||||
textInput: 'default text',
|
||||
paragraphInput: 'paragraph default',
|
||||
numberInput: 42,
|
||||
checkboxInput: false,
|
||||
})
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onVisionFilesChange).toHaveBeenCalledWith([])
|
||||
})
|
||||
|
||||
expect(screen.getByText('common.imageUploader.imageUpload')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should update inputs when user edits fields', async () => {
|
||||
const { onInputsChange, getInputsRef } = setup()
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onInputsChange).toHaveBeenCalled()
|
||||
})
|
||||
onInputsChange.mockClear()
|
||||
|
||||
fireEvent.change(screen.getByPlaceholderText('Text Input'), {
|
||||
target: { value: 'new text' },
|
||||
})
|
||||
fireEvent.change(screen.getByPlaceholderText('Paragraph Input'), {
|
||||
target: { value: 'paragraph value' },
|
||||
})
|
||||
fireEvent.change(screen.getByPlaceholderText('Number Input'), {
|
||||
target: { value: '99' },
|
||||
})
|
||||
|
||||
const label = screen.getByText('Checkbox Input')
|
||||
const checkbox = label.closest('div')?.parentElement?.querySelector('div')
|
||||
expect(checkbox).toBeTruthy()
|
||||
fireEvent.click(checkbox as HTMLElement)
|
||||
|
||||
const latest = onInputsChange.mock.calls[onInputsChange.mock.calls.length - 1][0]
|
||||
expect(latest).toEqual({
|
||||
textInput: 'new text',
|
||||
paragraphInput: 'paragraph value',
|
||||
numberInput: '99',
|
||||
checkboxInput: true,
|
||||
})
|
||||
expect(getInputsRef()?.current).toEqual(latest)
|
||||
})
|
||||
|
||||
it('should clear inputs when Clear button is pressed', async () => {
|
||||
const { onInputsChange } = setup()
|
||||
await waitFor(() => {
|
||||
expect(onInputsChange).toHaveBeenCalled()
|
||||
})
|
||||
onInputsChange.mockClear()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.clear' }))
|
||||
|
||||
expect(onInputsChange).toHaveBeenCalledWith({
|
||||
textInput: '',
|
||||
paragraphInput: '',
|
||||
numberInput: '',
|
||||
checkboxInput: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('should submit form and call onSend when Run button clicked', async () => {
|
||||
const { onSend, onInputsChange } = setup()
|
||||
await waitFor(() => {
|
||||
expect(onInputsChange).toHaveBeenCalled()
|
||||
})
|
||||
fireEvent.click(screen.getByTestId('run-button'))
|
||||
expect(onSend).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should display stop controls when runControl is provided', async () => {
|
||||
const onStop = jest.fn()
|
||||
const runControl = {
|
||||
onStop,
|
||||
isStopping: false,
|
||||
}
|
||||
const { onInputsChange } = setup({ runControl })
|
||||
await waitFor(() => {
|
||||
expect(onInputsChange).toHaveBeenCalled()
|
||||
})
|
||||
const stopButton = screen.getByTestId('stop-button')
|
||||
fireEvent.click(stopButton)
|
||||
expect(onStop).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should disable stop button while runControl is stopping', async () => {
|
||||
const runControl = {
|
||||
onStop: jest.fn(),
|
||||
isStopping: true,
|
||||
}
|
||||
const { onInputsChange } = setup({ runControl })
|
||||
await waitFor(() => {
|
||||
expect(onInputsChange).toHaveBeenCalled()
|
||||
})
|
||||
const stopButton = screen.getByTestId('stop-button')
|
||||
expect(stopButton).toBeDisabled()
|
||||
})
|
||||
})
|
||||
@ -57,6 +57,8 @@ const RunOnce: FC<IRunOnceProps> = ({
|
||||
promptConfig.prompt_variables.forEach((item) => {
|
||||
if (item.type === 'string' || item.type === 'paragraph')
|
||||
newInputs[item.key] = ''
|
||||
else if (item.type === 'number')
|
||||
newInputs[item.key] = ''
|
||||
else if (item.type === 'checkbox')
|
||||
newInputs[item.key] = false
|
||||
else
|
||||
@ -92,7 +94,7 @@ const RunOnce: FC<IRunOnceProps> = ({
|
||||
else if (item.type === 'string' || item.type === 'paragraph')
|
||||
newInputs[item.key] = item.default || ''
|
||||
else if (item.type === 'number')
|
||||
newInputs[item.key] = item.default
|
||||
newInputs[item.key] = item.default ?? ''
|
||||
else if (item.type === 'checkbox')
|
||||
newInputs[item.key] = item.default || false
|
||||
else if (item.type === 'file')
|
||||
@ -230,6 +232,7 @@ const RunOnce: FC<IRunOnceProps> = ({
|
||||
variant={isRunning ? 'secondary' : 'primary'}
|
||||
disabled={isRunning && runControl?.isStopping}
|
||||
onClick={handlePrimaryClick}
|
||||
data-testid={isRunning ? 'stop-button' : 'run-button'}
|
||||
>
|
||||
{isRunning ? (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user