From 5e75ffa03f7fce03cfde200a2cb3a2ffba208bd7 Mon Sep 17 00:00:00 2001 From: yyh Date: Mon, 6 Jul 2026 13:45:13 +0800 Subject: [PATCH] test: cover agent selector option refresh --- .../block-selector/__tests__/blocks.spec.tsx | 223 +++++++++++------- 1 file changed, 138 insertions(+), 85 deletions(-) diff --git a/web/app/components/workflow/block-selector/__tests__/blocks.spec.tsx b/web/app/components/workflow/block-selector/__tests__/blocks.spec.tsx index 75c1b30d434..ac93a9ef407 100644 --- a/web/app/components/workflow/block-selector/__tests__/blocks.spec.tsx +++ b/web/app/components/workflow/block-selector/__tests__/blocks.spec.tsx @@ -1,3 +1,7 @@ +import type { + AgentInviteOptionResponse, + AgentInviteOptionsResponse, +} from '@dify/contracts/api/console/agent/types.gen' import type { NodeDefault } from '../../types' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor } from '@testing-library/react' @@ -15,7 +19,7 @@ const runtimeState = vi.hoisted(() => ({ })) const queryMocks = vi.hoisted(() => ({ - inviteOptionsQueryFn: vi.fn(), + request: vi.fn(), toastError: vi.fn(), })) @@ -35,19 +39,8 @@ vi.mock('@/app/components/app/store', () => ({ }), })) -vi.mock('@/service/client', () => ({ - consoleQuery: { - agent: { - inviteOptions: { - get: { - queryOptions: (options: unknown) => ({ - queryKey: ['agents', 'invite-options', options], - queryFn: () => queryMocks.inviteOptionsQueryFn(options), - }), - }, - }, - }, - }, +vi.mock('@/service/base', () => ({ + request: (...args: unknown[]) => queryMocks.request(...args), })) vi.mock('@langgenius/dify-ui/toast', () => ({ @@ -74,6 +67,58 @@ const createBlock = ( checkValid: () => ({ isValid: true }), }) +const createInviteOption = ( + overrides: Partial & Pick, +): AgentInviteOptionResponse => { + const { id, name, ...rest } = overrides + + return { + id, + name, + description: rest.description ?? 'Clarification Drafter', + active_config_snapshot_id: rest.active_config_snapshot_id ?? 'version-1', + role: rest.role ?? 'Researcher', + agent_kind: rest.agent_kind ?? 'dify_agent', + icon: rest.icon ?? 'A', + icon_background: rest.icon_background ?? '#E9D7FE', + icon_type: rest.icon_type ?? 'emoji', + scope: rest.scope ?? 'roster', + source: rest.source ?? 'workflow', + status: rest.status ?? 'active', + ...rest, + } +} + +const createInviteOptionsResponse = ( + agents: AgentInviteOptionResponse[], +): AgentInviteOptionsResponse => ({ + data: agents, + has_more: false, + limit: 8, + page: 1, + total: agents.length, +}) + +const createJsonResponse = (body: unknown) => + new Response(JSON.stringify(body), { + status: 200, + headers: { + 'Content-Type': 'application/json', + }, + }) + +const mockInviteOptionsResponse = (agents: AgentInviteOptionResponse[]) => { + queryMocks.request.mockImplementation(() => Promise.resolve(createJsonResponse(createInviteOptionsResponse(agents)))) +} + +const expectLastInviteOptionsRequest = () => { + const [url] = queryMocks.request.mock.calls.at(-1) ?? [] + const requestURL = new URL(String(url), window.location.origin) + + expect(requestURL.pathname).toBe('/console/api/agent/invite-options') + return requestURL +} + describe('Blocks', () => { beforeEach(() => { vi.clearAllMocks() @@ -125,13 +170,7 @@ describe('Blocks', () => { it('opens the agent selector on Agent block hover', async () => { const user = userEvent.setup() - queryMocks.inviteOptionsQueryFn.mockResolvedValue({ - data: [], - has_more: false, - limit: 8, - page: 1, - total: 0, - }) + mockInviteOptionsResponse([]) const queryClient = new QueryClient({ defaultOptions: { queries: { @@ -171,28 +210,12 @@ describe('Blocks', () => { it('opens the agent selector from the Agent block and selects an agent', async () => { const user = userEvent.setup() const onSelect = vi.fn() - queryMocks.inviteOptionsQueryFn.mockResolvedValue({ - data: [ - { - id: 'agent-1', - name: 'Nadia', - description: 'Clarification Drafter', - active_config_snapshot_id: 'version-1', - role: 'Researcher', - agent_kind: 'dify_agent', - icon: 'A', - icon_background: '#E9D7FE', - icon_type: 'emoji', - scope: 'roster', - source: 'workflow', - status: 'active', - }, - ], - has_more: false, - limit: 8, - page: 1, - total: 1, - }) + mockInviteOptionsResponse([ + createInviteOption({ + id: 'agent-1', + name: 'Nadia', + }), + ]) const queryClient = new QueryClient({ defaultOptions: { @@ -246,42 +269,84 @@ describe('Blocks', () => { agent_node_kind: 'dify_agent', version: '2', }) - expect(queryMocks.inviteOptionsQueryFn).toHaveBeenCalledWith({ - input: { - query: { - app_id: 'app-1', - limit: 8, - page: 1, + const requestURL = expectLastInviteOptionsRequest() + expect(requestURL.searchParams.get('app_id')).toBe('app-1') + expect(requestURL.searchParams.get('limit')).toBe('8') + expect(requestURL.searchParams.get('page')).toBe('1') + }) + + it('should refresh Agent v2 roster options when the selector is reopened', async () => { + const user = userEvent.setup() + queryMocks.request + .mockImplementationOnce(() => Promise.resolve(createJsonResponse(createInviteOptionsResponse([ + createInviteOption({ + id: 'agent-1', + name: 'Nadia', + }), + ])))) + .mockImplementation(() => Promise.resolve(createJsonResponse(createInviteOptionsResponse([ + createInviteOption({ + id: 'agent-2', + name: 'Bruno', + role: 'Planner', + }), + ])))) + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + staleTime: 5 * 60 * 1000, }, }, }) + const hooksStore = createHooksStore({ + configsMap: { + flowId: 'app-1', + flowType: FlowType.appFlow, + fileSettings: {} as never, + }, + }) + + render( + + + + + , + ) + + await user.click(screen.getByRole('button', { name: /Agent/ })) + expect(await screen.findByText('Nadia')).toBeInTheDocument() + + await user.click(screen.getByRole('combobox', { name: 'agentV2.roster.searchLabel' })) + await user.keyboard('{Escape}') + await waitFor(() => { + expect(screen.queryByRole('dialog', { name: 'agentV2.roster.nodeSelector.dialogLabel' })).not.toBeInTheDocument() + }) + + await user.click(screen.getByRole('button', { name: /Agent/ })) + + expect(await screen.findByText('Bruno')).toBeInTheDocument() + expect(screen.getByText('Planner')).toBeInTheDocument() + await waitFor(() => expect(queryMocks.request).toHaveBeenCalledTimes(2)) + expect(screen.queryByText('Nadia')).not.toBeInTheDocument() }) it('does not select an Agent v2 roster agent without active config snapshot', async () => { const user = userEvent.setup() const onSelect = vi.fn() - queryMocks.inviteOptionsQueryFn.mockResolvedValue({ - data: [ - { - id: 'agent-1', - name: 'Nadia', - description: 'Clarification Drafter', - active_config_snapshot_id: null, - role: 'Researcher', - agent_kind: 'dify_agent', - icon: 'A', - icon_background: '#E9D7FE', - icon_type: 'emoji', - scope: 'roster', - source: 'workflow', - status: 'active', - }, - ], - has_more: false, - limit: 8, - page: 1, - total: 1, - }) + mockInviteOptionsResponse([ + createInviteOption({ + id: 'agent-1', + name: 'Nadia', + active_config_snapshot_id: null, + }), + ]) const queryClient = new QueryClient({ defaultOptions: { @@ -323,13 +388,7 @@ describe('Blocks', () => { it('inserts an inline Agent v2 node from the selector start action', async () => { const user = userEvent.setup() const onSelect = vi.fn() - queryMocks.inviteOptionsQueryFn.mockResolvedValue({ - data: [], - has_more: false, - limit: 8, - page: 1, - total: 0, - }) + mockInviteOptionsResponse([]) const queryClient = new QueryClient({ defaultOptions: { queries: { @@ -376,13 +435,7 @@ describe('Blocks', () => { it('closes the agent selector when Escape closes the combobox', async () => { const user = userEvent.setup() - queryMocks.inviteOptionsQueryFn.mockResolvedValue({ - data: [], - has_more: false, - limit: 8, - page: 1, - total: 0, - }) + mockInviteOptionsResponse([]) const queryClient = new QueryClient({ defaultOptions: { queries: {