Files
dify/web/app/components/workflow/__tests__/node-contextmenu.spec.tsx

104 lines
3.3 KiB
TypeScript

import type { Node } from '../types'
import { ContextMenu } from '@langgenius/dify-ui/context-menu'
import { fireEvent, render, screen } from '@testing-library/react'
import { NodeContextmenu } from '../node-contextmenu'
const mockUseNodes = vi.hoisted(() => vi.fn())
const mockUseStore = vi.hoisted(() => vi.fn())
const mockUseNodeActionsMenuModel = vi.hoisted(() => vi.fn())
vi.mock('@/app/components/workflow/store/workflow/use-nodes', () => ({
__esModule: true,
default: () => mockUseNodes(),
}))
vi.mock('@/app/components/workflow/store', () => ({
useStore: (selector: (state: { contextMenuTarget?: { type: 'node', nodeId: string } }) => unknown) => mockUseStore(selector),
}))
vi.mock('@/app/components/workflow/node-actions-menu/use-node-actions-menu-model', () => ({
useNodeActionsMenuModel: (props: unknown) => mockUseNodeActionsMenuModel(props),
}))
describe('NodeContextmenu', () => {
const mockClose = vi.fn()
let contextMenuTarget: { type: 'node', nodeId: string } | undefined
let nodes: Node[]
beforeEach(() => {
vi.clearAllMocks()
contextMenuTarget = undefined
nodes = [{
id: 'node-1',
type: 'custom',
position: { x: 0, y: 0 },
data: {
title: 'Node 1',
desc: '',
type: 'code' as never,
},
} as Node]
mockUseNodes.mockImplementation(() => nodes)
mockUseStore.mockImplementation((selector: (state: { contextMenuTarget?: { type: 'node', nodeId: string } }) => unknown) => selector({ contextMenuTarget }))
mockUseNodeActionsMenuModel.mockImplementation((props: { id: string, data: Node['data'], onClose: () => void }) => ({
about: {
author: 'Dify',
description: 'Node actions',
},
canChangeBlock: false,
canRun: false,
data: props.data,
handleCopy: props.onClose,
handleDelete: props.onClose,
handleDuplicate: props.onClose,
handleRun: props.onClose,
helpLinkUri: undefined,
id: props.id,
isSingleton: false,
isUndeletable: false,
nodesReadOnly: false,
sourceHandle: 'source',
workflowAppHref: undefined,
}))
})
const renderNodeContextmenu = () => render(
<ContextMenu open>
<NodeContextmenu onClose={mockClose} />
</ContextMenu>,
)
it('should stay hidden when the node menu is absent', () => {
renderNodeContextmenu()
expect(screen.queryByRole('button')).not.toBeInTheDocument()
expect(mockUseNodeActionsMenuModel).not.toHaveBeenCalled()
})
it('should stay hidden when the referenced node cannot be found', () => {
contextMenuTarget = { type: 'node', nodeId: 'missing-node' }
renderNodeContextmenu()
expect(screen.queryByRole('button')).not.toBeInTheDocument()
expect(mockUseNodeActionsMenuModel).not.toHaveBeenCalled()
})
it('should render the node actions and close from content actions', () => {
contextMenuTarget = { type: 'node', nodeId: 'node-1' }
renderNodeContextmenu()
expect(screen.getByText('WORKFLOW.PANEL.ABOUT')).toBeInTheDocument()
expect(mockUseNodeActionsMenuModel).toHaveBeenCalledWith(expect.objectContaining({
id: 'node-1',
data: expect.objectContaining({ title: 'Node 1' }),
showHelpLink: true,
}))
fireEvent.click(screen.getByRole('menuitem', { name: /workflow\.common\.copy/i }))
expect(mockClose).toHaveBeenCalledTimes(1)
})
})