From 4beffa97e0a367db48b077fa0f48d4e3d6beab45 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Thu, 3 Sep 2026 09:38:39 +0000 Subject: [PATCH] refactor(web): migrate runtime variable inputs (#41739) Co-authored-by: Joel --- oxlint-suppressions.json | 27 ---- .../debug/__tests__/chat-user-input.spec.tsx | 128 +++++------------- .../configuration/debug/chat-user-input.tsx | 27 ++-- .../__tests__/index.spec.tsx | 16 ++- .../prompt-value-panel/index.tsx | 30 ++-- .../overview/workflow-hidden-input-fields.tsx | 6 +- .../inputs-form/__tests__/content.spec.tsx | 10 +- .../chat-with-history/inputs-form/content.tsx | 25 +++- .../inputs-form/__tests__/content.spec.tsx | 12 +- .../embedded-chatbot/inputs-form/content.tsx | 25 +++- 10 files changed, 118 insertions(+), 188 deletions(-) diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json index c91d85911e4..8c87c9ae25d 100644 --- a/oxlint-suppressions.json +++ b/oxlint-suppressions.json @@ -368,14 +368,6 @@ "count": 1 } }, - "web/app/components/app/configuration/debug/chat-user-input.tsx": { - "jsx-a11y/no-autofocus": { - "count": 2 - }, - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/app/configuration/debug/debug-with-multiple-model/chat-item.tsx": { "typescript/no-explicit-any": { "count": 6 @@ -414,14 +406,6 @@ "count": 1 } }, - "web/app/components/app/configuration/prompt-value-panel/index.tsx": { - "jsx-a11y/no-autofocus": { - "count": 2 - }, - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/app/create-app-dialog/app-list/sidebar.tsx": { "erasable-syntax-only/enums": { "count": 1 @@ -464,11 +448,6 @@ "count": 1 } }, - "web/app/components/app/overview/workflow-hidden-input-fields.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "web/app/components/app/text-generate/item/index.tsx": { "typescript/no-explicit-any": { "count": 3 @@ -625,9 +604,6 @@ } }, "web/app/components/base/chat/chat-with-history/inputs-form/content.tsx": { - "no-restricted-imports": { - "count": 1 - }, "typescript/no-explicit-any": { "count": 3 } @@ -715,9 +691,6 @@ } }, "web/app/components/base/chat/embedded-chatbot/inputs-form/content.tsx": { - "no-restricted-imports": { - "count": 1 - }, "typescript/no-explicit-any": { "count": 3 } diff --git a/web/app/components/app/configuration/debug/__tests__/chat-user-input.spec.tsx b/web/app/components/app/configuration/debug/__tests__/chat-user-input.spec.tsx index 636d64fcd4d..bc5522517e8 100644 --- a/web/app/components/app/configuration/debug/__tests__/chat-user-input.spec.tsx +++ b/web/app/components/app/configuration/debug/__tests__/chat-user-input.spec.tsx @@ -11,37 +11,6 @@ vi.mock('use-context-selector', () => ({ createContext: vi.fn(() => ({})), })) -vi.mock('@/app/components/base/input', () => ({ - default: ({ - value, - onChange, - placeholder, - autoFocus, - maxLength, - readOnly, - type, - }: { - value: string - onChange: (e: { target: { value: string } }) => void - placeholder?: string - autoFocus?: boolean - maxLength?: number - readOnly?: boolean - type?: string - }) => ( - - ), -})) - vi.mock('@/app/components/workflow/nodes/_base/components/before-run-form/bool-input', () => ({ default: ({ name, @@ -172,7 +141,7 @@ describe('ChatUserInput', () => { ) render() - expect(screen.getByTestId('input-Name')).toBeInTheDocument() + expect(screen.getByRole('textbox', { name: 'Name' })).toBeInTheDocument() }) it('should render paragraph input type', () => { @@ -204,7 +173,7 @@ describe('ChatUserInput', () => { ) render() - const select = screen.getByRole('combobox') + const select = screen.getByRole('combobox', { name: 'Choice' }) await user.click(select) expect(await screen.findByRole('option', { name: 'A' })).toBeInTheDocument() expect(screen.getByRole('option', { name: 'B' })).toBeInTheDocument() @@ -221,7 +190,7 @@ describe('ChatUserInput', () => { ) render() - const input = screen.getByTestId('input-Count') + const input = screen.getByRole('spinbutton', { name: 'Count' }) expect(input).toBeInTheDocument() expect(input).toHaveAttribute('type', 'number') }) @@ -256,9 +225,9 @@ describe('ChatUserInput', () => { ) render() - expect(screen.getByTestId('input-Name')).toBeInTheDocument() + expect(screen.getByRole('textbox', { name: 'Name' })).toBeInTheDocument() expect(screen.getByRole('textbox', { name: 'Description' })).toBeInTheDocument() - expect(screen.getByRole('combobox')).toBeInTheDocument() + expect(screen.getByRole('combobox', { name: 'Choice' })).toBeInTheDocument() }) it('should show optional label for non-required fields', () => { @@ -313,7 +282,7 @@ describe('ChatUserInput', () => { ) render() - expect(screen.getByTestId('input-Name')).toHaveValue('John') + expect(screen.getByRole('textbox', { name: 'Name' })).toHaveValue('John') }) it('should display existing input values for paragraph type', () => { @@ -340,7 +309,7 @@ describe('ChatUserInput', () => { render() // Number type input still uses string value internally - expect(screen.getByTestId('input-Count')).toHaveValue(42) + expect(screen.getByRole('spinbutton', { name: 'Count' })).toHaveValue(42) }) it('should display checkbox as checked when value is truthy', () => { @@ -381,7 +350,7 @@ describe('ChatUserInput', () => { ) render() - expect(screen.getByTestId('input-Name')).toHaveValue('') + expect(screen.getByRole('textbox', { name: 'Name' })).toHaveValue('') }) it('should handle undefined values', () => { @@ -394,7 +363,7 @@ describe('ChatUserInput', () => { ) render() - expect(screen.getByTestId('input-Name')).toHaveValue('') + expect(screen.getByRole('textbox', { name: 'Name' })).toHaveValue('') }) }) @@ -409,7 +378,9 @@ describe('ChatUserInput', () => { ) render() - fireEvent.change(screen.getByTestId('input-Name'), { target: { value: 'New Value' } }) + fireEvent.change(screen.getByRole('textbox', { name: 'Name' }), { + target: { value: 'New Value' }, + }) expect(mockSetInputs).toHaveBeenCalledWith({ name: 'New Value' }) }) @@ -447,7 +418,7 @@ describe('ChatUserInput', () => { ) render() - await user.click(screen.getByRole('combobox')) + await user.click(screen.getByRole('combobox', { name: 'Choice' })) await user.click(await screen.findByRole('option', { name: 'B' })) expect(mockSetInputs).toHaveBeenCalledWith({ choice: 'B' }) @@ -463,7 +434,9 @@ describe('ChatUserInput', () => { ) render() - fireEvent.change(screen.getByTestId('input-Count'), { target: { value: '100' } }) + fireEvent.change(screen.getByRole('spinbutton', { name: 'Count' }), { + target: { value: '100' }, + }) expect(mockSetInputs).toHaveBeenCalledWith({ count: '100' }) }) @@ -508,7 +481,9 @@ describe('ChatUserInput', () => { render() - fireEvent.change(screen.getByTestId('input-Name'), { target: { value: 'Valid' } }) + fireEvent.change(screen.getByRole('textbox', { name: 'Name' }), { + target: { value: 'Valid' }, + }) expect(mockSetInputs).not.toHaveBeenCalled() }) @@ -527,7 +502,7 @@ describe('ChatUserInput', () => { ) render() - expect(screen.getByTestId('input-Name')).not.toHaveAttribute('readonly') + expect(screen.getByRole('textbox', { name: 'Name' })).not.toHaveAttribute('readonly') }) it('should set string input as readonly when test/run is denied even if configuration is editable', () => { @@ -542,7 +517,7 @@ describe('ChatUserInput', () => { ) render() - expect(screen.getByTestId('input-Name')).toHaveAttribute('readonly') + expect(screen.getByRole('textbox', { name: 'Name' })).toHaveAttribute('readonly') }) it('should set string input as readonly when configuration is readonly and test/run is denied', () => { @@ -557,7 +532,7 @@ describe('ChatUserInput', () => { ) render() - expect(screen.getByTestId('input-Name')).toHaveAttribute('readonly') + expect(screen.getByRole('textbox', { name: 'Name' })).toHaveAttribute('readonly') }) it('should set paragraph input as readonly when configuration is readonly and test/run is denied', () => { @@ -592,7 +567,7 @@ describe('ChatUserInput', () => { ) render() - expect(screen.getByRole('combobox')).toBeDisabled() + expect(screen.getByRole('combobox', { name: 'Choice' })).toBeDisabled() }) it('should disable checkbox when configuration is readonly and test/run is denied', () => { @@ -718,39 +693,8 @@ describe('ChatUserInput', () => { }) }) - describe('AutoFocus', () => { - it('should set autoFocus on first string input', () => { - mockUseContext.mockReturnValue( - createContextValue({ - modelConfig: createModelConfig([ - createPromptVariable({ key: 'first', name: 'First', type: 'string' }), - createPromptVariable({ key: 'second', name: 'Second', type: 'string' }), - ]), - }), - ) - - render() - expect(screen.getByTestId('input-First')).toHaveAttribute('data-autofocus', 'true') - expect(screen.getByTestId('input-Second')).not.toHaveAttribute('data-autofocus') - }) - - it('should set autoFocus on first number input when it is the first field', () => { - mockUseContext.mockReturnValue( - createContextValue({ - modelConfig: createModelConfig([ - createPromptVariable({ key: 'count', name: 'Count', type: 'number' }), - createPromptVariable({ key: 'name', name: 'Name', type: 'string' }), - ]), - }), - ) - - render() - expect(screen.getByTestId('input-Count')).toHaveAttribute('data-autofocus', 'true') - }) - }) - - describe('MaxLength', () => { - it('should pass maxLength to string input', () => { + describe('Input constraints', () => { + it('preserves the text length constraint', () => { mockUseContext.mockReturnValue( createContextValue({ modelConfig: createModelConfig([ @@ -760,20 +704,8 @@ describe('ChatUserInput', () => { ) render() - expect(screen.getByTestId('input-Name')).toHaveAttribute('maxLength', '50') - }) - it('should pass maxLength to number input', () => { - mockUseContext.mockReturnValue( - createContextValue({ - modelConfig: createModelConfig([ - createPromptVariable({ key: 'count', name: 'Count', type: 'number', max_length: 10 }), - ]), - }), - ) - - render() - expect(screen.getByTestId('input-Count')).toHaveAttribute('maxLength', '10') + expect(screen.getByRole('textbox', { name: 'Name' })).toHaveAttribute('maxLength', '50') }) }) @@ -789,7 +721,7 @@ describe('ChatUserInput', () => { ) render() - const select = screen.getByRole('combobox') + const select = screen.getByRole('combobox', { name: 'Choice' }) await user.click(select) expect(screen.queryAllByRole('option')).toHaveLength(0) }) @@ -805,7 +737,9 @@ describe('ChatUserInput', () => { ) render() - fireEvent.change(screen.getByTestId('input-Name'), { target: { value: 'Updated' } }) + fireEvent.change(screen.getByRole('textbox', { name: 'Name' }), { + target: { value: 'Updated' }, + }) expect(mockSetInputs).toHaveBeenCalledWith({ name: 'Updated', @@ -823,7 +757,7 @@ describe('ChatUserInput', () => { ) render() - expect(screen.getByTestId('input-Value')).toHaveValue('123') + expect(screen.getByRole('textbox', { name: 'Value' })).toHaveValue('123') }) it('should not hide label for checkbox type', () => { diff --git a/web/app/components/app/configuration/debug/chat-user-input.tsx b/web/app/components/app/configuration/debug/chat-user-input.tsx index 3ad495da64a..c52d9b77a7d 100644 --- a/web/app/components/app/configuration/debug/chat-user-input.tsx +++ b/web/app/components/app/configuration/debug/chat-user-input.tsx @@ -1,5 +1,6 @@ import type { Inputs } from '@/models/debug' import { cn } from '@langgenius/dify-ui/cn' +import { Input } from '@langgenius/dify-ui/input' import { Select, SelectContent, @@ -11,10 +12,9 @@ import { } from '@langgenius/dify-ui/select' import { Textarea } from '@langgenius/dify-ui/textarea' import * as React from 'react' -import { useEffect } from 'react' +import { useEffect, useId } from 'react' import { useTranslation } from 'react-i18next' import { useContext } from 'use-context-selector' -import Input from '@/app/components/base/input' import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input' import ConfigContext from '@/context/debug-configuration' @@ -24,6 +24,7 @@ type Props = Readonly<{ const ChatUserInput = ({ inputs }: Props) => { const { t } = useTranslation() + const baseId = useId() const { modelConfig, setInputs, canTestAndRun = false } = useContext(ConfigContext) const debugInputReadonly = !canTestAndRun @@ -81,12 +82,14 @@ const ChatUserInput = ({ inputs }: Props) => { )} >
- {promptVariables.map(({ key, name, type, options, max_length, required }, index) => ( + {promptVariables.map(({ key, name, type, options, max_length, required }) => (
{type !== 'checkbox' && (
-
{name || key}
+
+ {name || key} +
{!required && ( {t(($) => $['panel.optional'], { ns: 'workflow' })} @@ -97,12 +100,10 @@ const ChatUserInput = ({ inputs }: Props) => {
{type === 'string' && ( { - handleInputValueChange(key, e.target.value) - }} + onValueChange={(value) => handleInputValueChange(key, value)} placeholder={name} - autoFocus={index === 0} maxLength={max_length} readOnly={debugInputReadonly} /> @@ -110,7 +111,7 @@ const ChatUserInput = ({ inputs }: Props) => { {type === 'paragraph' && (