mirror of
https://github.com/langgenius/dify.git
synced 2026-08-30 17:11:50 +08:00
refactor(web): migrate pipeline publishing input form (#41129)
This commit is contained in:
@@ -2929,14 +2929,6 @@
|
||||
"count": 25
|
||||
}
|
||||
},
|
||||
"web/app/components/rag-pipeline/components/__tests__/publish-as-knowledge-pipeline-modal.spec.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
},
|
||||
"jsx-a11y/no-static-element-interactions": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/rag-pipeline/components/panel/input-field/editor/form/__tests__/hidden-fields.spec.tsx": {
|
||||
"eslint-react/static-components": {
|
||||
"count": 4
|
||||
@@ -3034,11 +3026,6 @@
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"web/app/components/rag-pipeline/components/publish-as-knowledge-pipeline-modal.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/rag-pipeline/components/publish-toast.tsx": {
|
||||
"jsx-a11y/click-events-have-key-events": {
|
||||
"count": 1
|
||||
|
||||
+41
-34
@@ -1,4 +1,5 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test'
|
||||
import PublishAsKnowledgePipelineModal from '../publish-as-knowledge-pipeline-modal'
|
||||
|
||||
@@ -16,25 +17,8 @@ vi.mock('@/app/components/workflow/store', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/input', () => ({
|
||||
default: ({ value, onChange, ...props }: Record<string, unknown>) => (
|
||||
<input
|
||||
data-testid="name-input"
|
||||
value={value as string}
|
||||
onChange={onChange as () => void}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/app-icon', () => ({
|
||||
default: ({ onClick }: { onClick?: () => void }) => (
|
||||
<div data-testid="app-icon" onClick={onClick} />
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('es-toolkit/function', () => ({
|
||||
noop: () => {},
|
||||
default: () => <div />,
|
||||
}))
|
||||
|
||||
describe('PublishAsKnowledgePipelineModal', () => {
|
||||
@@ -45,6 +29,12 @@ describe('PublishAsKnowledgePipelineModal', () => {
|
||||
onCancel: mockOnCancel,
|
||||
onConfirm: mockOnConfirm,
|
||||
}
|
||||
const getNameInput = () =>
|
||||
screen.getByRole('textbox', { name: 'pipeline.common.publishAsPipeline.name' })
|
||||
const getIconButton = () =>
|
||||
screen.getByRole('button', {
|
||||
name: 'common.operation.edit pipeline.common.publishAsPipeline.name',
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
@@ -57,14 +47,13 @@ describe('PublishAsKnowledgePipelineModal', () => {
|
||||
it('should render modal with title', () => {
|
||||
render(<PublishAsKnowledgePipelineModal {...defaultProps} />)
|
||||
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('pipeline.common.publishAs')).toBeInTheDocument()
|
||||
expect(screen.getByRole('dialog', { name: 'pipeline.common.publishAs' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should initialize with knowledgeName from store', () => {
|
||||
render(<PublishAsKnowledgePipelineModal {...defaultProps} />)
|
||||
|
||||
const nameInput = screen.getByTestId('name-input') as HTMLInputElement
|
||||
const nameInput = getNameInput() as HTMLInputElement
|
||||
expect(nameInput.value).toBe('Test Pipeline')
|
||||
})
|
||||
|
||||
@@ -93,6 +82,15 @@ describe('PublishAsKnowledgePipelineModal', () => {
|
||||
expect(mockOnCancel).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should call onCancel when Escape is pressed', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(<PublishAsKnowledgePipelineModal {...defaultProps} />)
|
||||
|
||||
await user.keyboard('{Escape}')
|
||||
|
||||
expect(mockOnCancel).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should call onConfirm with name, icon, and description when confirm clicked', () => {
|
||||
render(<PublishAsKnowledgePipelineModal {...defaultProps} />)
|
||||
|
||||
@@ -108,7 +106,7 @@ describe('PublishAsKnowledgePipelineModal', () => {
|
||||
it('should update pipeline name when input changes', () => {
|
||||
render(<PublishAsKnowledgePipelineModal {...defaultProps} />)
|
||||
|
||||
const nameInput = screen.getByTestId('name-input')
|
||||
const nameInput = getNameInput()
|
||||
fireEvent.change(nameInput, { target: { value: 'New Name' } })
|
||||
|
||||
expect((nameInput as HTMLInputElement).value).toBe('New Name')
|
||||
@@ -125,14 +123,20 @@ describe('PublishAsKnowledgePipelineModal', () => {
|
||||
expect((textarea as HTMLTextAreaElement).value).toBe('My description')
|
||||
})
|
||||
|
||||
it('should disable confirm button when name is empty', () => {
|
||||
it('should not submit with Enter when name is empty', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(<PublishAsKnowledgePipelineModal {...defaultProps} />)
|
||||
|
||||
const nameInput = screen.getByTestId('name-input')
|
||||
fireEvent.change(nameInput, { target: { value: '' } })
|
||||
const nameInput = getNameInput()
|
||||
await user.clear(nameInput)
|
||||
|
||||
const confirmBtn = screen.getByText('workflow.common.publish')
|
||||
expect(confirmBtn).toBeDisabled()
|
||||
|
||||
await user.click(nameInput)
|
||||
await user.keyboard('{Enter}')
|
||||
|
||||
expect(mockOnConfirm).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should disable confirm button when confirmDisabled is true', () => {
|
||||
@@ -155,7 +159,7 @@ describe('PublishAsKnowledgePipelineModal', () => {
|
||||
|
||||
expect(screen.queryByPlaceholderText('Search emojis...')).not.toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByTestId('app-icon'))
|
||||
fireEvent.click(getIconButton())
|
||||
|
||||
expect(screen.getByPlaceholderText('Search emojis...')).toBeInTheDocument()
|
||||
})
|
||||
@@ -163,7 +167,7 @@ describe('PublishAsKnowledgePipelineModal', () => {
|
||||
it('should update icon when emoji style is selected', async () => {
|
||||
render(<PublishAsKnowledgePipelineModal {...defaultProps} />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('app-icon'))
|
||||
fireEvent.click(getIconButton())
|
||||
fireEvent.click(screen.getByRole('button', { name: '#E4FBCC' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: /iconPicker\.ok/ }))
|
||||
|
||||
@@ -175,7 +179,7 @@ describe('PublishAsKnowledgePipelineModal', () => {
|
||||
it('should keep icon picker open until confirmation', () => {
|
||||
render(<PublishAsKnowledgePipelineModal {...defaultProps} />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('app-icon'))
|
||||
fireEvent.click(getIconButton())
|
||||
fireEvent.click(screen.getByRole('button', { name: '#E4FBCC' }))
|
||||
|
||||
expect(screen.getByPlaceholderText('Search emojis...')).toBeInTheDocument()
|
||||
@@ -184,7 +188,7 @@ describe('PublishAsKnowledgePipelineModal', () => {
|
||||
it('should close icon picker when cancel is clicked', async () => {
|
||||
render(<PublishAsKnowledgePipelineModal {...defaultProps} />)
|
||||
|
||||
fireEvent.click(screen.getByTestId('app-icon'))
|
||||
fireEvent.click(getIconButton())
|
||||
fireEvent.click(screen.getByRole('button', { name: /iconPicker\.cancel/ }))
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -192,18 +196,21 @@ describe('PublishAsKnowledgePipelineModal', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should trim name and description before submitting', () => {
|
||||
it('should trim name and description when submitted with Enter', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(<PublishAsKnowledgePipelineModal {...defaultProps} />)
|
||||
|
||||
const nameInput = screen.getByTestId('name-input')
|
||||
fireEvent.change(nameInput, { target: { value: ' Trimmed Name ' } })
|
||||
const nameInput = getNameInput()
|
||||
await user.clear(nameInput)
|
||||
await user.type(nameInput, ' Trimmed Name ')
|
||||
|
||||
const textarea = screen.getByRole('textbox', {
|
||||
name: 'pipeline.common.publishAsPipeline.description',
|
||||
})
|
||||
fireEvent.change(textarea, { target: { value: ' Some desc ' } })
|
||||
await user.type(textarea, ' Some desc ')
|
||||
|
||||
fireEvent.click(screen.getByText('workflow.common.publish'))
|
||||
await user.click(nameInput)
|
||||
await user.keyboard('{Enter}')
|
||||
|
||||
expect(mockOnConfirm).toHaveBeenCalledWith('Trimmed Name', expect.any(Object), 'Some desc')
|
||||
})
|
||||
|
||||
+58
-38
@@ -2,14 +2,15 @@
|
||||
import type { AppIconSelection } from '@/app/components/base/app-icon-picker'
|
||||
import type { IconInfo } from '@/models/datasets'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { Dialog, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import { Field, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { IconButton } from '@langgenius/dify-ui/icon-button'
|
||||
import { Input } from '@langgenius/dify-ui/input'
|
||||
import { Textarea } from '@langgenius/dify-ui/textarea'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import AppIconPicker from '@/app/components/base/app-icon-picker'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
|
||||
type PublishAsKnowledgePipelineModalProps = {
|
||||
@@ -50,33 +51,47 @@ const PublishAsKnowledgePipelineModal = ({
|
||||
}, [])
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (confirmDisabled) return
|
||||
const name = pipelineName?.trim()
|
||||
if (!name || confirmDisabled) return
|
||||
|
||||
onConfirm(pipelineName?.trim() || '', pipelineIcon, description?.trim())
|
||||
onConfirm(name, pipelineIcon, description?.trim())
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open>
|
||||
<DialogContent className="w-full max-w-120! overflow-hidden! border-none p-0! text-left align-middle">
|
||||
<div className="relative flex items-center p-6 pr-14 pb-3 title-2xl-semi-bold text-text-primary">
|
||||
{t(($) => $['common.publishAs'], { ns: 'pipeline' })}
|
||||
<button
|
||||
type="button"
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open) onCancel()
|
||||
}}
|
||||
>
|
||||
<DialogContent className="w-full max-w-120! overflow-hidden! border-none p-0! text-left align-middle">
|
||||
<form
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault()
|
||||
handleConfirm()
|
||||
}}
|
||||
>
|
||||
<div className="relative flex items-center p-6 pr-14 pb-3">
|
||||
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{t(($) => $['common.publishAs'], { ns: 'pipeline' })}
|
||||
</DialogTitle>
|
||||
<IconButton
|
||||
aria-label={t(($) => $['operation.close'], { ns: 'common' })}
|
||||
className="absolute top-5 right-5 flex size-8 cursor-pointer items-center justify-center border-none bg-transparent p-0"
|
||||
size="lg"
|
||||
className="absolute top-5 right-5"
|
||||
onClick={onCancel}
|
||||
>
|
||||
<RiCloseLine className="size-4 text-text-tertiary" aria-hidden="true" />
|
||||
</button>
|
||||
<span aria-hidden="true" className="i-ri-close-line size-4" />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className="px-6 py-3">
|
||||
<div className="mb-5 flex">
|
||||
<div className="mr-3 grow">
|
||||
<div className="mb-1 flex h-6 items-center system-sm-medium text-text-secondary">
|
||||
<Field className="mr-3 grow" name="name">
|
||||
<FieldLabel>
|
||||
{t(($) => $['common.publishAsPipeline.name'], { ns: 'pipeline' })}
|
||||
</div>
|
||||
</FieldLabel>
|
||||
<Input
|
||||
autoComplete="off"
|
||||
value={pipelineName}
|
||||
onChange={(e) => setPipelineName(e.target.value)}
|
||||
placeholder={
|
||||
@@ -84,26 +99,31 @@ const PublishAsKnowledgePipelineModal = ({
|
||||
''
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<AppIcon
|
||||
size="xxl"
|
||||
</Field>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`${t(($) => $['operation.edit'], { ns: 'common' })} ${t(($) => $['common.publishAsPipeline.name'], { ns: 'pipeline' })}`}
|
||||
onClick={() => {
|
||||
setShowAppIconPicker(true)
|
||||
}}
|
||||
className="mt-2 shrink-0 cursor-pointer"
|
||||
iconType={pipelineIcon?.icon_type}
|
||||
icon={pipelineIcon?.icon}
|
||||
background={pipelineIcon?.icon_background}
|
||||
imageUrl={pipelineIcon?.icon_url}
|
||||
/>
|
||||
className="mt-2 shrink-0 cursor-pointer rounded-2xl focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden"
|
||||
>
|
||||
<AppIcon
|
||||
size="xxl"
|
||||
iconType={pipelineIcon?.icon_type}
|
||||
icon={pipelineIcon?.icon}
|
||||
background={pipelineIcon?.icon_background}
|
||||
imageUrl={pipelineIcon?.icon_url}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<div className="mb-1 flex h-6 items-center system-sm-medium text-text-secondary">
|
||||
<Field name="description">
|
||||
<FieldLabel>
|
||||
{t(($) => $['common.publishAsPipeline.description'], { ns: 'pipeline' })}
|
||||
</div>
|
||||
</FieldLabel>
|
||||
<Textarea
|
||||
autoComplete="off"
|
||||
className="resize-none"
|
||||
aria-label={t(($) => $['common.publishAsPipeline.description'], { ns: 'pipeline' })}
|
||||
placeholder={
|
||||
t(($) => $['common.publishAsPipeline.descriptionPlaceholder'], {
|
||||
ns: 'pipeline',
|
||||
@@ -112,16 +132,16 @@ const PublishAsKnowledgePipelineModal = ({
|
||||
value={description}
|
||||
onValueChange={(value) => setDescription(value)}
|
||||
/>
|
||||
</div>
|
||||
</Field>
|
||||
</div>
|
||||
<div className="flex items-center justify-end px-6 py-5">
|
||||
<Button className="mr-2" onClick={onCancel}>
|
||||
<div className="flex items-center justify-end gap-2 px-6 py-5">
|
||||
<Button type="button" onClick={onCancel}>
|
||||
{t(($) => $['operation.cancel'], { ns: 'common' })}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={!pipelineName?.trim() || confirmDisabled}
|
||||
variant="primary"
|
||||
onClick={() => handleConfirm()}
|
||||
>
|
||||
{t(($) => $['common.publish'], { ns: 'workflow' })}
|
||||
</Button>
|
||||
@@ -138,9 +158,9 @@ const PublishAsKnowledgePipelineModal = ({
|
||||
onSelect={handleSelectIcon}
|
||||
/>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user