|
6 | 6 | import { createRef } from 'react' |
7 | 7 |
|
8 | 8 | import { fireEvent, render, waitFor } from '@testing-library/react' |
| 9 | +import mockAxios from 'axios' |
9 | 10 |
|
10 | | -import type { AgentFlowInstance, FlowData } from './core/types' |
| 11 | +import type { AgentFlowInstance, AgentflowProps, FlowData } from './core/types' |
11 | 12 | import { Agentflow } from './Agentflow' |
12 | 13 |
|
13 | 14 | // Mock external dependencies - implementations in __mocks__/ |
14 | 15 | jest.mock('reactflow') |
15 | 16 | jest.mock('axios') |
16 | 17 |
|
| 18 | +const mockGet = mockAxios.get as jest.Mock |
| 19 | + |
17 | 20 | // Mock GenerateFlowDialog to expose callbacks for testing |
18 | 21 | jest.mock('./features/generator', () => ({ |
19 | 22 | GenerateFlowDialog: ({ |
@@ -482,4 +485,106 @@ describe('Agentflow Component', () => { |
482 | 485 | expect(document.getElementById('agentflow-css-variables')).not.toBeInTheDocument() |
483 | 486 | }) |
484 | 487 | }) |
| 488 | + |
| 489 | + describe('Auto Start Node Initialization', () => { |
| 490 | + const startNodeSchema = { |
| 491 | + name: 'startAgentflow', |
| 492 | + label: 'Start', |
| 493 | + type: 'Start', |
| 494 | + category: 'Agent Flows', |
| 495 | + description: 'Start node', |
| 496 | + baseClasses: ['Start'], |
| 497 | + inputs: [], |
| 498 | + outputs: [], |
| 499 | + version: 1 |
| 500 | + } |
| 501 | + |
| 502 | + beforeEach(() => { |
| 503 | + // Mock API to return a startAgentflow node definition |
| 504 | + mockGet.mockImplementation((url: string) => { |
| 505 | + if (typeof url === 'string' && url.includes('/nodes')) { |
| 506 | + return Promise.resolve({ data: [startNodeSchema] }) |
| 507 | + } |
| 508 | + return Promise.resolve({ data: [] }) |
| 509 | + }) |
| 510 | + }) |
| 511 | + |
| 512 | + afterEach(() => { |
| 513 | + // Restore default mock (empty array) |
| 514 | + mockGet.mockImplementation(() => Promise.resolve({ data: [] })) |
| 515 | + }) |
| 516 | + |
| 517 | + /** Helper: render without initialFlow and assert Start node via ref */ |
| 518 | + const renderAndGetNodes = async (initialFlow?: FlowData | null) => { |
| 519 | + const ref = createRef<AgentFlowInstance>() |
| 520 | + const props: Record<string, unknown> = { apiBaseUrl: 'https://example.com', ref } |
| 521 | + if (initialFlow !== undefined) props.initialFlow = initialFlow |
| 522 | + |
| 523 | + render(<Agentflow {...(props as unknown as AgentflowProps)} />) |
| 524 | + |
| 525 | + await waitFor(() => { |
| 526 | + expect(ref.current).toBeDefined() |
| 527 | + const flow = ref.current!.getFlow() |
| 528 | + expect(flow.nodes.length).toBeGreaterThan(0) |
| 529 | + }) |
| 530 | + return ref.current!.getFlow().nodes |
| 531 | + } |
| 532 | + |
| 533 | + it('should auto-add Start node when initialFlow is undefined', async () => { |
| 534 | + const nodes = await renderAndGetNodes() |
| 535 | + expect(nodes).toHaveLength(1) |
| 536 | + expect(nodes[0].id).toBe('startAgentflow_0') |
| 537 | + expect(nodes[0].data.name).toBe('startAgentflow') |
| 538 | + expect(nodes[0].data.label).toBe('Start') |
| 539 | + }) |
| 540 | + |
| 541 | + it('should auto-add Start node when initialFlow is null', async () => { |
| 542 | + const nodes = await renderAndGetNodes(null) |
| 543 | + expect(nodes).toHaveLength(1) |
| 544 | + expect(nodes[0].data.name).toBe('startAgentflow') |
| 545 | + }) |
| 546 | + |
| 547 | + it('should auto-add Start node when initialFlow has empty nodes', async () => { |
| 548 | + const emptyFlow: FlowData = { nodes: [], edges: [], viewport: { x: 0, y: 0, zoom: 1 } } |
| 549 | + const nodes = await renderAndGetNodes(emptyFlow) |
| 550 | + expect(nodes).toHaveLength(1) |
| 551 | + expect(nodes[0].data.name).toBe('startAgentflow') |
| 552 | + }) |
| 553 | + |
| 554 | + it('should auto-add Start node when initialFlow is empty object', async () => { |
| 555 | + const nodes = await renderAndGetNodes({} as FlowData) |
| 556 | + expect(nodes).toHaveLength(1) |
| 557 | + expect(nodes[0].data.name).toBe('startAgentflow') |
| 558 | + }) |
| 559 | + |
| 560 | + it('should handle initialFlow with unrelated fields without errors', async () => { |
| 561 | + const illegalFlow = { foo: 'bar', baz: 123 } as unknown as FlowData |
| 562 | + const nodes = await renderAndGetNodes(illegalFlow) |
| 563 | + |
| 564 | + expect(nodes).toHaveLength(1) |
| 565 | + expect(nodes[0].data.name).toBe('startAgentflow') |
| 566 | + }) |
| 567 | + |
| 568 | + it('should handle initialFlow with wrong types for nodes/edges without crashing', async () => { |
| 569 | + const illegalFlow = { nodes: 'not-an-array', edges: 42 } as unknown as FlowData |
| 570 | + const nodes = await renderAndGetNodes(illegalFlow) |
| 571 | + |
| 572 | + // Non-array nodes/edges are safely ignored, auto-init adds Start node |
| 573 | + expect(nodes).toHaveLength(1) |
| 574 | + expect(nodes[0].data.name).toBe('startAgentflow') |
| 575 | + }) |
| 576 | + |
| 577 | + it('should not auto-add Start node when initialFlow has nodes', async () => { |
| 578 | + const ref = createRef<AgentFlowInstance>() |
| 579 | + render(<Agentflow {...defaultProps} initialFlow={mockFlow} ref={ref} />) |
| 580 | + |
| 581 | + await waitFor(() => { |
| 582 | + expect(ref.current).toBeDefined() |
| 583 | + }) |
| 584 | + const nodes = ref.current!.getFlow().nodes |
| 585 | + // Should only have the one node from mockFlow, no duplicate Start added |
| 586 | + const startNodes = nodes.filter((n) => n.data.name === 'startAgentflow') |
| 587 | + expect(startNodes).toHaveLength(1) |
| 588 | + }) |
| 589 | + }) |
485 | 590 | }) |
0 commit comments