diff --git a/web/app/components/workflow/hooks/__tests__/use-available-blocks.spec.ts b/web/app/components/workflow/hooks/__tests__/use-available-blocks.spec.ts index a237f6df114..df354ccf3f0 100644 --- a/web/app/components/workflow/hooks/__tests__/use-available-blocks.spec.ts +++ b/web/app/components/workflow/hooks/__tests__/use-available-blocks.spec.ts @@ -145,7 +145,7 @@ describe('useAvailableBlocks', () => { }) describe('inContainer filtering', () => { - it('should exclude Iteration, Loop, End, DataSource, KnowledgeBase, HumanInput when inContainer=true', () => { + it('should allow HumanInput while excluding unsupported blocks when inContainer=true', () => { const { result } = renderWorkflowHook(() => useAvailableBlocks(BlockEnum.LLM, true), { hooksStoreProps, }) @@ -155,7 +155,7 @@ describe('useAvailableBlocks', () => { expect(result.current.availableNextBlocks).not.toContain(BlockEnum.End) expect(result.current.availableNextBlocks).not.toContain(BlockEnum.DataSource) expect(result.current.availableNextBlocks).not.toContain(BlockEnum.KnowledgeBase) - expect(result.current.availableNextBlocks).not.toContain(BlockEnum.HumanInput) + expect(result.current.availableNextBlocks).toContain(BlockEnum.HumanInput) }) it('should exclude LoopEnd when not in container', () => { diff --git a/web/app/components/workflow/hooks/__tests__/use-nodes-interactions.spec.ts b/web/app/components/workflow/hooks/__tests__/use-nodes-interactions.spec.ts index 0eab5ad8af2..2d2353b6971 100644 --- a/web/app/components/workflow/hooks/__tests__/use-nodes-interactions.spec.ts +++ b/web/app/components/workflow/hooks/__tests__/use-nodes-interactions.spec.ts @@ -1183,15 +1183,14 @@ describe('useNodesInteractions', () => { ) }) - // Nested container paste restrictions should stay aligned with available block filtering. - describe('nested container paste restrictions', () => { + // Nested container paste behavior should stay aligned with available block filtering. + describe('nested container paste behavior', () => { const disallowedNestedPasteNodeTypes = [ BlockEnum.End, BlockEnum.Iteration, BlockEnum.Loop, BlockEnum.DataSource, BlockEnum.KnowledgeBase, - BlockEnum.HumanInput, ] const createNodeMeta = (type: BlockEnum) => ({ @@ -1205,7 +1204,7 @@ describe('useNodesInteractions', () => { }, }) - const runDisallowedPasteScenario = async ( + const pasteNodeIntoContainer = async ( containerType: BlockEnum.Iteration | BlockEnum.Loop, nodeType: BlockEnum, ) => { @@ -1263,23 +1262,48 @@ describe('useNodesInteractions', () => { const pastedNodes = rfState.setNodes.mock.calls.at(-1)?.[0] as Node[] - expect(pastedNodes).toHaveLength(1) - expect(pastedNodes[0]?.id).toBe(containerId) - expect(pastedNodes[0]?.data._children).toEqual([]) - expect( - pastedNodes.some((node) => node.data.type === nodeType && node.parentId === containerId), - ).toBe(false) + return { containerId, pastedNodes } } it.each(disallowedNestedPasteNodeTypes)( 'should not paste %s into an iteration container', async (nodeType) => { - await runDisallowedPasteScenario(BlockEnum.Iteration, nodeType) + const { containerId, pastedNodes } = await pasteNodeIntoContainer( + BlockEnum.Iteration, + nodeType, + ) + + expect(pastedNodes).toHaveLength(1) + expect(pastedNodes[0]?.id).toBe(containerId) + expect(pastedNodes[0]?.data._children).toEqual([]) }, ) - it('should not paste human-input into a loop container', async () => { - await runDisallowedPasteScenario(BlockEnum.Loop, BlockEnum.HumanInput) - }) + it.each([BlockEnum.Iteration, BlockEnum.Loop] as const)( + 'should paste human-input into a %s container', + async (containerType) => { + const { containerId, pastedNodes } = await pasteNodeIntoContainer( + containerType, + BlockEnum.HumanInput, + ) + const container = pastedNodes.find((node) => node.id === containerId) + const pastedHumanInput = pastedNodes.find( + (node) => node.data.type === BlockEnum.HumanInput && node.parentId === containerId, + ) + const isIteration = containerType === BlockEnum.Iteration + + expect(pastedHumanInput).toBeDefined() + expect(pastedHumanInput?.data).toMatchObject({ + isInIteration: isIteration, + iteration_id: isIteration ? containerId : undefined, + isInLoop: !isIteration, + loop_id: isIteration ? undefined : containerId, + }) + expect(container?.data._children).toContainEqual({ + nodeId: pastedHumanInput?.id, + nodeType: BlockEnum.HumanInput, + }) + }, + ) }) }) diff --git a/web/app/components/workflow/hooks/use-available-blocks.ts b/web/app/components/workflow/hooks/use-available-blocks.ts index 675a36be49a..6ebb1933f28 100644 --- a/web/app/components/workflow/hooks/use-available-blocks.ts +++ b/web/app/components/workflow/hooks/use-available-blocks.ts @@ -11,8 +11,7 @@ const availableBlocksFilter = (nodeType: BlockEnum, inContainer?: boolean) => { nodeType === BlockEnum.Loop || nodeType === BlockEnum.End || nodeType === BlockEnum.DataSource || - nodeType === BlockEnum.KnowledgeBase || - nodeType === BlockEnum.HumanInput) + nodeType === BlockEnum.KnowledgeBase) ) return false diff --git a/web/app/components/workflow/hooks/use-nodes-interactions.ts b/web/app/components/workflow/hooks/use-nodes-interactions.ts index 4b2ea7bf6a8..6d38ddf853f 100644 --- a/web/app/components/workflow/hooks/use-nodes-interactions.ts +++ b/web/app/components/workflow/hooks/use-nodes-interactions.ts @@ -1786,7 +1786,6 @@ export const useNodesInteractions = () => { BlockEnum.Loop, BlockEnum.DataSource, BlockEnum.KnowledgeBase, - BlockEnum.HumanInput, ] // Same-canvas copy keeps the source container selected, so only treat a // selected container as the paste target when it is not part of the clipboard.