import { describe, it, expect, vi } from 'vitest' import { render, screen, fireEvent } from '@testing-library/react' import { EdgeModal } from '../EdgeModal' describe('EdgeModal', () => { // ── Visibility ──────────────────────────────────────────────────────────── it('renders nothing when closed', () => { const { container } = render() expect(container.querySelector('[role="dialog"]')).toBeNull() }) it('renders form when open', () => { render() expect(screen.getByText('Connect Nodes')).toBeDefined() }) it('uses custom title when provided', () => { render() expect(screen.getByText('Edit Link')).toBeDefined() }) // ── Submit button label ─────────────────────────────────────────────────── it('shows "Connect" button when onDelete is not provided', () => { render() expect(screen.getByRole('button', { name: 'Connect' })).toBeDefined() }) it('shows "Save" button when onDelete is provided', () => { render() expect(screen.getByRole('button', { name: 'Save' })).toBeDefined() }) // ── Default submit ──────────────────────────────────────────────────────── it('calls onSubmit with default ethernet type', () => { const onSubmit = vi.fn() const onClose = vi.fn() render() fireEvent.click(screen.getByRole('button', { name: 'Connect' })) expect(onSubmit).toHaveBeenCalledOnce() expect(onSubmit.mock.calls[0][0].type).toBe('ethernet') expect(onClose).toHaveBeenCalledOnce() }) it('calls onSubmit with label when filled', () => { const onSubmit = vi.fn() render() fireEvent.change(screen.getByPlaceholderText('e.g. 1G, trunk...'), { target: { value: 'uplink' } }) fireEvent.click(screen.getByRole('button', { name: 'Connect' })) expect(onSubmit.mock.calls[0][0].label).toBe('uplink') }) it('omits label from payload when empty', () => { const onSubmit = vi.fn() render() fireEvent.click(screen.getByRole('button', { name: 'Connect' })) expect(onSubmit.mock.calls[0][0].label).toBeUndefined() }) // ── VLAN ID field ───────────────────────────────────────────────────────── it('does not show VLAN ID field for ethernet type', () => { render() expect(screen.queryByPlaceholderText('e.g. 20')).toBeNull() }) it('submits integer vlan_id when type is vlan', () => { const onSubmit = vi.fn() render() fireEvent.click(screen.getByRole('button', { name: 'Connect' })) expect(onSubmit.mock.calls[0][0].vlan_id).toBe(20) }) it('omits vlan_id from payload for non-vlan types', () => { const onSubmit = vi.fn() render() fireEvent.click(screen.getByRole('button', { name: 'Connect' })) expect(onSubmit.mock.calls[0][0].vlan_id).toBeUndefined() }) // ── Path style ──────────────────────────────────────────────────────────── it('defaults to bezier path style', () => { const onSubmit = vi.fn() render() fireEvent.click(screen.getByRole('button', { name: 'Connect' })) expect(onSubmit.mock.calls[0][0].path_style).toBe('bezier') }) it('switches path style to smooth on click', () => { const onSubmit = vi.fn() render() fireEvent.click(screen.getByText('Smooth step')) fireEvent.click(screen.getByRole('button', { name: 'Connect' })) expect(onSubmit.mock.calls[0][0].path_style).toBe('smooth') }) // ── Animation select ────────────────────────────────────────────────────── it('animation defaults to None — animated omitted from payload', () => { const onSubmit = vi.fn() render() fireEvent.click(screen.getByRole('button', { name: 'Connect' })) expect(onSubmit.mock.calls[0][0].animated).toBeUndefined() }) it('selecting Snake sends animated: "snake"', () => { const onSubmit = vi.fn() render() fireEvent.click(screen.getByText('Snake')) fireEvent.click(screen.getByRole('button', { name: 'Connect' })) expect(onSubmit.mock.calls[0][0].animated).toBe('snake') }) it('selecting Flow sends animated: "flow"', () => { const onSubmit = vi.fn() render() fireEvent.click(screen.getByText('Flow')) fireEvent.click(screen.getByRole('button', { name: 'Connect' })) expect(onSubmit.mock.calls[0][0].animated).toBe('flow') }) it('selecting None after Snake omits animated from payload', () => { const onSubmit = vi.fn() render() fireEvent.click(screen.getByText('Snake')) fireEvent.click(screen.getByText('None')) fireEvent.click(screen.getByRole('button', { name: 'Connect' })) expect(onSubmit.mock.calls[0][0].animated).toBeUndefined() }) it('pre-fills animation from initial "snake" string', () => { const onSubmit = vi.fn() render() fireEvent.click(screen.getByRole('button', { name: 'Connect' })) expect(onSubmit.mock.calls[0][0].animated).toBe('snake') }) it('pre-fills animation from legacy initial true (backward compat)', () => { const onSubmit = vi.fn() render() fireEvent.click(screen.getByRole('button', { name: 'Connect' })) expect(onSubmit.mock.calls[0][0].animated).toBe('snake') }) // ── Pre-fill ────────────────────────────────────────────────────────────── it('pre-fills label from initial prop', () => { render() const input = screen.getByPlaceholderText('e.g. 1G, trunk...') as HTMLInputElement expect(input.value).toBe('trunk') }) it('pre-fills path style from initial prop', () => { const onSubmit = vi.fn() render() fireEvent.click(screen.getByRole('button', { name: 'Connect' })) expect(onSubmit.mock.calls[0][0].path_style).toBe('smooth') }) // ── Cancel & Delete ─────────────────────────────────────────────────────── it('calls onClose when Cancel is clicked', () => { const onClose = vi.fn() render() fireEvent.click(screen.getByRole('button', { name: 'Cancel' })) expect(onClose).toHaveBeenCalledOnce() }) it('shows Delete button when onDelete is provided', () => { render() expect(screen.getByRole('button', { name: 'Delete' })).toBeDefined() }) it('does not show Delete button without onDelete', () => { render() expect(screen.queryByRole('button', { name: 'Delete' })).toBeNull() }) it('calls onDelete and onClose when Delete is clicked', () => { const onDelete = vi.fn() const onClose = vi.fn() render() fireEvent.click(screen.getByRole('button', { name: 'Delete' })) expect(onDelete).toHaveBeenCalledOnce() expect(onClose).toHaveBeenCalledOnce() }) })