diff --git a/web/app/(humanInputLayout)/form/[token]/__tests__/form.spec.tsx b/web/app/(humanInputLayout)/form/[token]/__tests__/form.spec.tsx
new file mode 100644
index 0000000000..0106fd564d
--- /dev/null
+++ b/web/app/(humanInputLayout)/form/[token]/__tests__/form.spec.tsx
@@ -0,0 +1,164 @@
+import type { FormData } from '../form'
+import type { FileEntity } from '@/app/components/base/file-uploader/types'
+import { render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { UserActionButtonType } from '@/app/components/workflow/nodes/human-input/types'
+import { InputVarType, SupportUploadFileTypes } from '@/app/components/workflow/types'
+import { TransferMethod } from '@/types/app'
+import FormContent from '../form'
+
+const mockSubmitForm = vi.hoisted(() => vi.fn())
+const mockUseGetHumanInputForm = vi.hoisted(() => vi.fn())
+
+vi.mock('@/next/navigation', () => ({
+ useParams: () => ({ token: 'token-123' }),
+}))
+
+vi.mock('@/service/use-share', () => ({
+ useGetHumanInputForm: (...args: unknown[]) => mockUseGetHumanInputForm(...args),
+ useSubmitHumanInputForm: () => ({
+ mutate: mockSubmitForm,
+ isPending: false,
+ }),
+}))
+
+vi.mock('@/hooks/use-document-title', () => ({
+ __esModule: true,
+ default: vi.fn(),
+}))
+
+vi.mock('@/app/components/base/chat/chat/answer/human-input-content/content-item', () => ({
+ __esModule: true,
+ default: ({ content, onInputChange }: { content: string, onInputChange: (name: string, value: unknown) => void }) => (
+
+ {content}
+
+
+
+ ),
+}))
+
+vi.mock('@/app/components/base/chat/chat/answer/human-input-content/expiration-time', () => ({
+ __esModule: true,
+ default: () => expiration-time
,
+}))
+
+vi.mock('@/app/components/base/loading', () => ({
+ __esModule: true,
+ default: () => loading
,
+}))
+
+vi.mock('@/app/components/base/logo/dify-logo', () => ({
+ __esModule: true,
+ default: () => dify-logo
,
+}))
+
+vi.mock('@/app/components/base/app-icon', () => ({
+ __esModule: true,
+ default: () => app-icon
,
+}))
+
+describe('Human input share form', () => {
+ const formData: FormData = {
+ site: {
+ site: {
+ title: 'Review App',
+ icon_type: 'emoji',
+ icon: 'R',
+ icon_background: '#fff',
+ icon_url: '',
+ default_language: 'en-US',
+ description: '',
+ copyright: '',
+ privacy_policy: '',
+ custom_disclaimer: '',
+ prompt_public: false,
+ use_icon_as_answer_icon: false,
+ },
+ },
+ form_content: '{{#$output.summary#}} {{#$output.attachments#}}',
+ inputs: [
+ {
+ type: InputVarType.paragraph,
+ output_variable_name: 'summary',
+ default: {
+ type: 'constant',
+ value: 'initial summary',
+ selector: [],
+ },
+ },
+ {
+ type: InputVarType.multiFiles,
+ output_variable_name: 'attachments',
+ allowed_file_extensions: ['.pdf'],
+ allowed_file_types: [SupportUploadFileTypes.document],
+ allowed_file_upload_methods: [TransferMethod.local_file],
+ max_upload_count: 3,
+ },
+ ],
+ resolved_default_values: {},
+ user_actions: [
+ {
+ id: 'approve',
+ title: 'Approve',
+ button_style: UserActionButtonType.Primary,
+ },
+ ],
+ expiration_time: 60,
+ }
+
+ beforeEach(() => {
+ vi.clearAllMocks()
+ mockUseGetHumanInputForm.mockReturnValue({
+ data: formData,
+ isLoading: false,
+ error: null,
+ })
+ })
+
+ it('submits typed human input values through the share form mutation', async () => {
+ const user = userEvent.setup()
+
+ render()
+
+ await user.click(screen.getAllByRole('button', { name: 'share-update-summary' })[0]!)
+ await user.click(screen.getAllByRole('button', { name: 'share-update-attachments' })[0]!)
+ await user.click(screen.getByRole('button', { name: 'Approve' }))
+
+ expect(mockSubmitForm).toHaveBeenCalledWith({
+ token: 'token-123',
+ data: {
+ action: 'approve',
+ inputs: {
+ summary: 'updated summary',
+ attachments: [{
+ id: 'file-1',
+ name: 'review.pdf',
+ size: 128,
+ type: 'document',
+ progress: 100,
+ transferMethod: TransferMethod.local_file,
+ supportFileType: 'document',
+ } satisfies FileEntity],
+ },
+ },
+ }, expect.objectContaining({
+ onSuccess: expect.any(Function),
+ }))
+ })
+})
diff --git a/web/app/components/workflow/nodes/human-input/components/__tests__/single-run-form.spec.tsx b/web/app/components/workflow/nodes/human-input/components/__tests__/single-run-form.spec.tsx
new file mode 100644
index 0000000000..55edfc5e58
--- /dev/null
+++ b/web/app/components/workflow/nodes/human-input/components/__tests__/single-run-form.spec.tsx
@@ -0,0 +1,106 @@
+import type { FileEntity } from '@/app/components/base/file-uploader/types'
+import type { HumanInputFormData } from '@/types/workflow'
+import { render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { InputVarType, SupportUploadFileTypes } from '@/app/components/workflow/types'
+import { TransferMethod } from '@/types/app'
+import { UserActionButtonType } from '../../types'
+import SingleRunForm from '../single-run-form'
+
+vi.mock('@/app/components/base/chat/chat/answer/human-input-content/content-item', () => ({
+ __esModule: true,
+ default: ({ content, onInputChange }: { content: string, onInputChange: (name: string, value: unknown) => void }) => (
+
+ {content}
+
+
+
+ ),
+}))
+
+describe('SingleRunForm', () => {
+ const formData: HumanInputFormData = {
+ form_id: 'form-1',
+ node_id: 'node-1',
+ node_title: 'Human Input',
+ form_content: '{{#$output.summary#}} {{#$output.attachments#}}',
+ inputs: [
+ {
+ type: InputVarType.paragraph,
+ output_variable_name: 'summary',
+ default: {
+ type: 'constant',
+ value: 'initial summary',
+ selector: [],
+ },
+ },
+ {
+ type: InputVarType.multiFiles,
+ output_variable_name: 'attachments',
+ allowed_file_extensions: ['.pdf'],
+ allowed_file_types: [SupportUploadFileTypes.document],
+ allowed_file_upload_methods: [TransferMethod.local_file],
+ max_upload_count: 3,
+ },
+ ],
+ actions: [
+ {
+ id: 'approve',
+ title: 'Approve',
+ button_style: UserActionButtonType.Primary,
+ },
+ ],
+ form_token: 'token-1',
+ resolved_default_values: {},
+ display_in_ui: true,
+ expiration_time: 0,
+ }
+
+ it('submits typed human input values from the single-run form', async () => {
+ const user = userEvent.setup()
+ const onSubmit = vi.fn().mockResolvedValue(undefined)
+
+ render(
+ ,
+ )
+
+ await user.click(screen.getAllByRole('button', { name: 'update-summary' })[0]!)
+ await user.click(screen.getAllByRole('button', { name: 'update-attachments' })[0]!)
+ await user.click(screen.getByRole('button', { name: 'Approve' }))
+
+ expect(onSubmit).toHaveBeenCalledWith({
+ action: 'approve',
+ inputs: {
+ summary: 'updated summary',
+ attachments: [{
+ id: 'file-1',
+ name: 'review.pdf',
+ size: 128,
+ type: 'document',
+ progress: 100,
+ transferMethod: TransferMethod.local_file,
+ supportFileType: 'document',
+ } satisfies FileEntity],
+ },
+ })
+ })
+})