import { fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { describe, expect, it, vi } from 'vitest'; import type { AdminChannel } from '@/api/adminApi'; import { RouteConfigModal } from './RouteConfigModal'; const channel = (id: string, overrides: Partial = {}): AdminChannel => ({ id, name: `通道${id}`, code: `CODE-${id}`, carriers: ['mobile'], sendRegion: '广东省', gatewayHost: '127.0.0.1', gatewayPort: 7890, account: 'test', srcId: '106', rateLimitPerSecond: 1, unitPrice: 325, status: 'active', ...overrides, }); describe('RouteConfigModal', () => { it('keeps historical priority and exposes costs while permitting a disconnected active channel', async () => { const onSubmit = vi.fn(() => null); const current = channel('current', { connectionStates: [] }); render( , ); expect(screen.getByRole('spinbutton', { name: /优先级/ })).toHaveValue(20); expect(screen.getByText('0.0325')).toBeVisible(); expect(screen.getByText('暂无连接回写')).toBeVisible(); expect(screen.getByRole('radio')).toBeEnabled(); await userEvent.click(screen.getByRole('button', { name: /^确认$/ })); expect(onSubmit).toHaveBeenCalledWith({ id: 'route-1', channelId: 'current', priority: 20 }); }); it('shows unavailable reasons and keeps the selected channel while searching', async () => { const channels = [ channel('available'), channel('occupied'), channel('deleted', { status: 'deleted' }), channel('telecom', { carriers: ['telecom'] }), channel('disabled', { status: 'disabled' }), ]; render( null} />, ); for (const id of ['occupied', 'deleted', 'telecom']) expect(screen.getByRole('radio', { name: new RegExp(`CODE-${id}`) })).toBeDisabled(); expect(screen.getByText('已在当前通道组中配置')).toBeVisible(); expect(screen.getByText('通道已删除')).toBeVisible(); expect(screen.getByText('不支持移动')).toBeVisible(); expect(screen.getByText('已停用')).toBeVisible(); expect(screen.getByRole('radio', { name: /CODE-disabled/ })).toBeEnabled(); await userEvent.click(screen.getByRole('radio', { name: /CODE-available/ })); await userEvent.type(screen.getByRole('textbox', { name: '搜索通道' }), 'CODE-telecom'); expect(screen.getAllByRole('radio')).toHaveLength(1); expect(screen.getByText('已选:通道available(CODE-available)')).toBeVisible(); }); it('disables region mismatches but preserves a historical province option', () => { render( null} />, ); expect(screen.getByText('浙江省')).toBeVisible(); expect(screen.getByRole('radio')).toBeDisabled(); expect(screen.getByText('通道地区与所选省份不匹配')).toBeVisible(); expect(screen.getByRole('alert')).toHaveTextContent('原通道不存在,请重新选择'); }); it('retains form values after parent conflict rejection and rejects a fractional priority', async () => { const onSubmit = vi.fn(() => '同一通道组内全国通道优先级不能重复'); const onClose = vi.fn(); render( , ); await userEvent.click(screen.getByRole('radio')); await userEvent.click(screen.getByRole('button', { name: /^确认$/ })); expect(screen.getByRole('alert')).toHaveTextContent('优先级不能重复'); expect(screen.getByRole('radio')).toBeChecked(); expect(onClose).not.toHaveBeenCalled(); const input = screen.getByRole('spinbutton', { name: /优先级/ }); fireEvent.change(input, { target: { value: '1.5' } }); await userEvent.click(screen.getByRole('button', { name: /^确认$/ })); expect(screen.getByRole('alert')).toHaveTextContent('整数'); expect(onSubmit).toHaveBeenCalledTimes(1); fireEvent.change(input, { target: { value: '-10' } }); await userEvent.click(screen.getByRole('button', { name: /^确认$/ })); expect(onSubmit).toHaveBeenLastCalledWith(expect.objectContaining({ priority: -10 })); }); it('uses the shared unsaved guard when dismissing a changed selection', async () => { const onClose = vi.fn(); render( null} />, ); await userEvent.click(screen.getByRole('radio')); await userEvent.click(screen.getByRole('button', { name: /^取消$/ })); expect(screen.getByRole('alertdialog')).toHaveTextContent('放弃未保存的修改'); expect(onClose).not.toHaveBeenCalled(); await userEvent.click(screen.getByRole('button', { name: '继续编辑' })); expect(screen.getByRole('radio')).toBeChecked(); }); });