mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 18:00:59 -04:00
121 lines
4.2 KiB
TypeScript
121 lines
4.2 KiB
TypeScript
import type { Node } from '../types'
|
|
import { fireEvent, render, screen } from '@testing-library/react'
|
|
import { NodeContextmenu } from '../node-contextmenu'
|
|
|
|
const mockUseNodes = vi.hoisted(() => vi.fn())
|
|
const mockUsePanelInteractions = vi.hoisted(() => vi.fn())
|
|
const mockUseStore = vi.hoisted(() => vi.fn())
|
|
const mockNodeActionsContextMenuContent = vi.hoisted(() => vi.fn())
|
|
const mockContextMenuContent = vi.hoisted(() => vi.fn())
|
|
|
|
vi.mock('@langgenius/dify-ui/context-menu', () => ({
|
|
ContextMenu: ({ children, onOpenChange }: { children: React.ReactNode, onOpenChange: (open: boolean) => void }) => (
|
|
<div>
|
|
{children}
|
|
<button type="button" onClick={() => onOpenChange(false)}>close-context-menu</button>
|
|
</div>
|
|
),
|
|
ContextMenuContent: ({ children, positionerProps, popupClassName }: { children: React.ReactNode, positionerProps?: { anchor?: unknown }, popupClassName?: string }) => {
|
|
mockContextMenuContent({ positionerProps, popupClassName })
|
|
return <div>{children}</div>
|
|
},
|
|
}))
|
|
|
|
vi.mock('@/app/components/workflow/store/workflow/use-nodes', () => ({
|
|
__esModule: true,
|
|
default: () => mockUseNodes(),
|
|
}))
|
|
|
|
vi.mock('@/app/components/workflow/hooks', () => ({
|
|
usePanelInteractions: () => mockUsePanelInteractions(),
|
|
}))
|
|
|
|
vi.mock('@/app/components/workflow/store', () => ({
|
|
useStore: (selector: (state: { nodeMenu?: { nodeId: string, clientX: number, clientY: number } }) => unknown) => mockUseStore(selector),
|
|
}))
|
|
|
|
vi.mock('@/app/components/workflow/node-actions-menu/context-menu-content', () => ({
|
|
NodeActionsContextMenuContent: (props: {
|
|
id: string
|
|
data: Node['data']
|
|
showHelpLink: boolean
|
|
onClose: () => void
|
|
}) => {
|
|
mockNodeActionsContextMenuContent(props)
|
|
return (
|
|
<button type="button" onClick={props.onClose}>
|
|
{props.id}
|
|
:
|
|
{props.data.title}
|
|
</button>
|
|
)
|
|
},
|
|
}))
|
|
|
|
describe('NodeContextmenu', () => {
|
|
const mockHandleNodeContextmenuCancel = vi.fn()
|
|
let nodeMenu: { nodeId: string, clientX: number, clientY: number } | undefined
|
|
let nodes: Node[]
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
nodeMenu = 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)
|
|
mockUsePanelInteractions.mockReturnValue({
|
|
handleNodeContextmenuCancel: mockHandleNodeContextmenuCancel,
|
|
})
|
|
mockUseStore.mockImplementation((selector: (state: { nodeMenu?: { nodeId: string, clientX: number, clientY: number } }) => unknown) => selector({ nodeMenu }))
|
|
})
|
|
|
|
it('should stay hidden when the node menu is absent', () => {
|
|
render(<NodeContextmenu />)
|
|
|
|
expect(screen.queryByRole('button')).not.toBeInTheDocument()
|
|
expect(mockNodeActionsContextMenuContent).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should stay hidden when the referenced node cannot be found', () => {
|
|
nodeMenu = { nodeId: 'missing-node', clientX: 80, clientY: 120 }
|
|
|
|
render(<NodeContextmenu />)
|
|
|
|
expect(screen.queryByRole('button')).not.toBeInTheDocument()
|
|
expect(mockNodeActionsContextMenuContent).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should render the context menu at the stored pointer position and close on content/root actions', () => {
|
|
nodeMenu = { nodeId: 'node-1', clientX: 80, clientY: 120 }
|
|
render(<NodeContextmenu />)
|
|
|
|
expect(screen.getByText('node-1:Node 1')).toBeInTheDocument()
|
|
expect(mockNodeActionsContextMenuContent).toHaveBeenCalledWith(expect.objectContaining({
|
|
id: 'node-1',
|
|
data: expect.objectContaining({ title: 'Node 1' }),
|
|
showHelpLink: true,
|
|
}))
|
|
expect(mockContextMenuContent).toHaveBeenCalledWith(expect.objectContaining({
|
|
popupClassName: 'w-[240px] rounded-lg',
|
|
}))
|
|
const anchor = mockContextMenuContent.mock.calls[0]![0].positionerProps.anchor as { getBoundingClientRect: () => DOMRect }
|
|
const rect = anchor.getBoundingClientRect()
|
|
expect(rect.x).toBe(80)
|
|
expect(rect.y).toBe(120)
|
|
|
|
fireEvent.click(screen.getByText('node-1:Node 1'))
|
|
fireEvent.click(screen.getByText('close-context-menu'))
|
|
|
|
expect(mockHandleNodeContextmenuCancel).toHaveBeenCalledTimes(2)
|
|
})
|
|
})
|