mirror of
https://github.com/langgenius/dify.git
synced 2026-11-13 06:41:07 +08:00
180 lines
6.2 KiB
TypeScript
180 lines
6.2 KiB
TypeScript
import type { WorkflowCommentDetail } from '@dify/contracts/api/console/apps/types.gen'
|
|
import type { ReactElement } from 'react'
|
|
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
|
import userEvent from '@testing-library/user-event'
|
|
import { createConsoleQueryWrapper } from '@/test/console/query-data'
|
|
import { renderWorkflowFlowComponent } from '../__tests__/workflow-test-env'
|
|
import { CommentThread } from './thread'
|
|
|
|
const render = (ui: ReactElement) => {
|
|
const { queryClient } = createConsoleQueryWrapper({
|
|
accountProfile: { id: 'user-1', name: 'Alice', avatar_url: 'alice.png' },
|
|
})
|
|
return renderWorkflowFlowComponent(<div id="workflow-container">{ui}</div>, {
|
|
queryClient,
|
|
initialStoreState: {
|
|
mentionableUsersCache: {
|
|
'app-1': [
|
|
{ id: 'user-1', name: 'Alice', email: 'alice@example.com', avatar_url: 'alice.png' },
|
|
{ id: 'user-2', name: 'Bob', email: 'bob@example.com', avatar_url: 'bob.png' },
|
|
],
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
const createComment = (): WorkflowCommentDetail => ({
|
|
id: 'comment-1',
|
|
position_x: 120,
|
|
position_y: 80,
|
|
content: '@Alice original comment',
|
|
created_by: 'user-1',
|
|
created_by_account: {
|
|
id: 'user-1',
|
|
name: 'Alice',
|
|
email: 'alice@example.com',
|
|
avatar_url: 'alice.png',
|
|
},
|
|
created_at: 1,
|
|
updated_at: 2,
|
|
resolved: false,
|
|
mentions: [],
|
|
replies: [
|
|
{
|
|
id: 'reply-1',
|
|
content: 'first reply',
|
|
created_by: 'user-1',
|
|
created_by_account: {
|
|
id: 'user-1',
|
|
name: 'Alice',
|
|
email: 'alice@example.com',
|
|
avatar_url: 'alice.png',
|
|
},
|
|
created_at: 2,
|
|
},
|
|
],
|
|
})
|
|
|
|
describe('CommentThread', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
it('triggers header actions and closes on Escape', async () => {
|
|
const user = userEvent.setup()
|
|
const onClose = vi.fn()
|
|
const onDelete = vi.fn()
|
|
const onResolve = vi.fn()
|
|
const onPrev = vi.fn()
|
|
const onNext = vi.fn()
|
|
|
|
render(
|
|
<CommentThread
|
|
comment={createComment()}
|
|
onClose={onClose}
|
|
onDelete={onDelete}
|
|
onResolve={onResolve}
|
|
onPrev={onPrev}
|
|
onNext={onNext}
|
|
canGoPrev
|
|
canGoNext
|
|
/>,
|
|
)
|
|
|
|
await user.click(screen.getByLabelText('workflowComments.comments.aria.deleteComment'))
|
|
await user.click(screen.getByLabelText('workflowComments.comments.aria.resolveComment'))
|
|
await user.click(screen.getByLabelText('workflowComments.comments.aria.previousComment'))
|
|
await user.click(screen.getByLabelText('workflowComments.comments.aria.nextComment'))
|
|
await user.click(screen.getByLabelText('workflowComments.comments.aria.closeComment'))
|
|
|
|
fireEvent.keyDown(document.body, { key: 'Escape' })
|
|
expect(onClose).toHaveBeenCalledTimes(1)
|
|
fireEvent.keyDown(screen.getByRole('dialog'), { key: 'Escape' })
|
|
|
|
expect(onDelete).toHaveBeenCalledTimes(1)
|
|
expect(onResolve).toHaveBeenCalledTimes(1)
|
|
expect(onPrev).toHaveBeenCalledTimes(1)
|
|
expect(onNext).toHaveBeenCalledTimes(1)
|
|
expect(onClose).toHaveBeenCalledTimes(2)
|
|
})
|
|
|
|
it('supports editing the root comment when the current user owns the thread', async () => {
|
|
const user = userEvent.setup()
|
|
const onCommentEdit = vi.fn()
|
|
|
|
render(
|
|
<CommentThread comment={createComment()} onClose={vi.fn()} onCommentEdit={onCommentEdit} />,
|
|
)
|
|
|
|
await user.click(screen.getByLabelText('workflowComments.comments.aria.commentActions'))
|
|
await user.click(screen.getByText('workflowComments.comments.actions.editComment'))
|
|
const editor = screen.getByPlaceholderText('workflowComments.comments.placeholder.editComment')
|
|
await user.clear(editor)
|
|
await user.type(editor, 'Updated root comment')
|
|
await user.click(screen.getByRole('button', { name: 'common.operation.save' }))
|
|
|
|
await waitFor(() => {
|
|
expect(onCommentEdit).toHaveBeenCalledWith('Updated root comment', [])
|
|
})
|
|
})
|
|
|
|
it('submits reply and updates preview hovering state on mouse enter/leave', async () => {
|
|
const user = userEvent.setup()
|
|
const onReply = vi.fn()
|
|
const { store } = render(
|
|
<CommentThread comment={createComment()} onClose={vi.fn()} onReply={onReply} />,
|
|
)
|
|
|
|
const dialog = screen.getByRole('dialog')
|
|
fireEvent.mouseEnter(dialog)
|
|
expect(store.getState().isCommentPreviewHovering).toBe(true)
|
|
fireEvent.mouseLeave(dialog)
|
|
expect(store.getState().isCommentPreviewHovering).toBe(false)
|
|
|
|
const input = screen.getByPlaceholderText('workflowComments.comments.placeholder.reply')
|
|
await user.type(input, '@Bo')
|
|
await user.keyboard('{Enter}')
|
|
await waitFor(() => expect(input).toHaveValue('@Bob '))
|
|
await user.type(input, 'please review')
|
|
await user.click(screen.getByRole('button', { name: 'common.operation.send' }))
|
|
await waitFor(() => expect(onReply).toHaveBeenCalledWith('@Bob please review', ['user-2']))
|
|
})
|
|
|
|
it('supports editing and direct deleting an existing reply', async () => {
|
|
const user = userEvent.setup()
|
|
const onReplyEdit = vi.fn()
|
|
const onReplyDeleteDirect = vi.fn()
|
|
|
|
render(
|
|
<CommentThread
|
|
comment={createComment()}
|
|
onClose={vi.fn()}
|
|
onReplyEdit={onReplyEdit}
|
|
onReplyDeleteDirect={onReplyDeleteDirect}
|
|
/>,
|
|
)
|
|
|
|
await user.click(screen.getByLabelText('workflowComments.comments.aria.replyActions'))
|
|
await user.click(await screen.findByText('workflowComments.comments.actions.editReply'))
|
|
const editor = screen.getByPlaceholderText('workflowComments.comments.placeholder.editReply')
|
|
await user.clear(editor)
|
|
await user.type(editor, 'Updated reply')
|
|
await user.click(screen.getByRole('button', { name: 'common.operation.save' }))
|
|
|
|
await waitFor(() => {
|
|
expect(onReplyEdit).toHaveBeenCalledWith('reply-1', 'Updated reply', [])
|
|
})
|
|
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.getByLabelText('workflowComments.comments.aria.replyActions'),
|
|
).toBeInTheDocument()
|
|
})
|
|
await user.click(screen.getByLabelText('workflowComments.comments.aria.replyActions'))
|
|
await user.click(await screen.findByText('workflowComments.comments.actions.deleteReply'))
|
|
await user.click(await screen.findByRole('button', { name: 'common.operation.delete' }))
|
|
|
|
expect(onReplyDeleteDirect).toHaveBeenCalledWith('reply-1')
|
|
})
|
|
})
|