refactor(web): migrate human input timeout field (#41050)

This commit is contained in:
yyh 2026-08-21 06:38:20 +00:00 committed by GitHub
parent 34fdac2113
commit 2ff3f7b533
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 18 additions and 40 deletions

View File

@ -4049,11 +4049,6 @@
"count": 1
}
},
"web/app/components/workflow/nodes/human-input/components/timeout.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/workflow/nodes/human-input/types.ts": {
"erasable-syntax-only/enums": {
"count": 2

View File

@ -1,4 +1,4 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { withSelectorKey } from '@/test/i18n-mock'
import TimeoutInput from '../timeout'
@ -9,22 +9,6 @@ vi.mock('react-i18next', () => ({
useTranslation: () => mockUseTranslation(),
}))
vi.mock('@/app/components/base/input', () => ({
__esModule: true,
default: (props: {
value: number
disabled?: boolean
onChange: (event: { target: { value: string } }) => void
}) => (
<input
data-testid="timeout-input"
value={props.value}
disabled={props.disabled}
onChange={(e) => props.onChange({ target: { value: e.target.value } })}
/>
),
}))
describe('TimeoutInput', () => {
const onChange = vi.fn()
@ -35,29 +19,32 @@ describe('TimeoutInput', () => {
})
})
it('should update the numeric timeout value and switch units', async () => {
it('should update the timeout with the keyboard and switch units', async () => {
const user = userEvent.setup()
render(<TimeoutInput timeout={3} unit="day" onChange={onChange} />)
fireEvent.change(screen.getByTestId('timeout-input'), { target: { value: '12' } })
const timeoutInput = screen.getByRole('textbox', { name: 'nodes.humanInput.timeout.title' })
await user.click(timeoutInput)
await user.keyboard('{ArrowUp}')
await user.click(screen.getByRole('radio', { name: 'nodes.humanInput.timeout.hours' }))
expect(onChange).toHaveBeenNthCalledWith(1, { timeout: 12, unit: 'day' })
expect(onChange).toHaveBeenNthCalledWith(1, { timeout: 4, unit: 'day' })
expect(onChange).toHaveBeenNthCalledWith(2, { timeout: 3, unit: 'hour' })
})
it('should fall back to 1 on invalid input and stay read-only when disabled', async () => {
it('should fall back to 1 when cleared and stay read-only when disabled', async () => {
const user = userEvent.setup()
const { rerender } = render(<TimeoutInput timeout={5} unit="hour" onChange={onChange} />)
fireEvent.change(screen.getByTestId('timeout-input'), { target: { value: 'abc' } })
const timeoutInput = screen.getByRole('textbox', { name: 'nodes.humanInput.timeout.title' })
await user.clear(timeoutInput)
expect(onChange).toHaveBeenCalledWith({ timeout: 1, unit: 'hour' })
rerender(<TimeoutInput timeout={5} unit="hour" onChange={onChange} readonly />)
await user.click(screen.getByRole('radio', { name: 'nodes.humanInput.timeout.days' }))
expect(onChange).toHaveBeenCalledTimes(1)
expect(screen.getByTestId('timeout-input')).toBeDisabled()
expect(screen.getByRole('textbox', { name: 'nodes.humanInput.timeout.title' })).toBeDisabled()
expect(screen.getByRole('radio', { name: 'nodes.humanInput.timeout.days' })).toBeDisabled()
})
})

View File

@ -1,8 +1,7 @@
import type { FC } from 'react'
import { NumberField, NumberFieldGroup, NumberFieldInput } from '@langgenius/dify-ui/number-field'
import { SegmentedControl, SegmentedControlItem } from '@langgenius/dify-ui/segmented-control'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import Input from '@/app/components/base/input'
const i18nPrefix = 'nodes.humanInput'
@ -22,21 +21,18 @@ const TimeoutInput: FC<Props> = ({ timeout, unit, onChange, readonly }) => {
const daysLabel = t(($) => $[`${i18nPrefix}.timeout.days`], { ns: 'workflow' })
const hoursLabel = t(($) => $[`${i18nPrefix}.timeout.hours`], { ns: 'workflow' })
const handleValueChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
if (/^\d*$/.test(value)) onChange({ timeout: Number(value) || 1, unit })
else onChange({ timeout: 1, unit })
}
return (
<div className="flex items-center gap-1">
<Input
wrapperClassName="w-16"
type="number"
<NumberField
value={timeout}
min={1}
onChange={handleValueChange}
onValueChange={(value) => onChange({ timeout: value ?? 1, unit })}
disabled={readonly}
/>
>
<NumberFieldGroup className="w-16">
<NumberFieldInput aria-label={timeoutLabel} />
</NumberFieldGroup>
</NumberField>
<SegmentedControl<'day' | 'hour'>
value={unit}
onValueChange={(unit) => onChange({ timeout, unit })}