import { describe, expect, it, vi } from 'vitest'; import '@testing-library/jest-dom/vitest'; import { screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ConfigProvider } from 'antd'; import InboundFormModal from '@/pages/inbounds/form/InboundFormModal'; import { DBInbound } from '@/models/dbinbound'; import { HttpUtil } from '@/utils'; import { renderWithProviders } from './test-utils'; function renderInbound(mode: 'add' | 'edit' = 'add') { const inbound = new DBInbound({ id: 41, protocol: 'vless', enable: true, port: 14443, settings: { clients: [], decryption: 'none', encryption: 'none' }, streamSettings: { network: 'tcp', security: 'none', tcpSettings: {} }, }); renderWithProviders( {}} onSaved={() => {}} /> , ); return userEvent.setup(); } describe('Inbound screen-reader interaction', () => { it('exposes the selected protocol as the closed control value', () => { renderInbound(); expect(screen.getByRole('combobox', { name: 'Protocol' })).toHaveDisplayValue('vless'); }); it('exposes all visible protocol options as selectable items within its dialog', async () => { const user = renderInbound(); const dialog = screen.getByRole('dialog', { name: 'Add Inbound' }); const protocol = within(dialog).getByRole('combobox', { name: 'Protocol' }); const shadowsocks = within(protocol).getByRole('option', { name: 'shadowsocks' }); expect(within(protocol).getByRole('option', { name: 'tuic' })).toBeInTheDocument(); expect( within(protocol).getByRole('option', { name: 'vless', selected: true }), ).toBeInTheDocument(); await user.selectOptions(protocol, shadowsocks); expect(protocol).toHaveDisplayValue('shadowsocks'); await user.click(screen.getByRole('tab', { name: 'Protocol' })); expect(screen.getByRole('combobox', { name: 'Encryption method' })).toBeVisible(); }); it('selects transport by its option role and submits the actual create payload', async () => { const post = vi.mocked(HttpUtil.post).mockClear(); const user = renderInbound(); await user.click(screen.getByRole('tab', { name: 'Stream' })); const transport = screen.getByRole('combobox', { name: 'Transmission' }); await user.selectOptions( transport, within(transport).getByRole('option', { name: 'WebSocket' }), ); const path = screen.getByRole('textbox', { name: 'Path' }); await user.clear(path); await user.type(path, '/accessible'); await user.click(screen.getByRole('button', { name: 'Create' })); await waitFor(() => expect(post).toHaveBeenCalledWith('/panel/api/inbounds/add', expect.anything()), ); const payload = post.mock.calls.find( ([url]) => url === '/panel/api/inbounds/add', )![1] as Record; expect(JSON.parse(String(payload.streamSettings))).toMatchObject({ network: 'ws', security: 'none', wsSettings: { path: '/accessible' }, }); }); it('preserves the disabled protocol in edit mode and exposes a named security group', async () => { const user = renderInbound('edit'); expect(screen.getByRole('combobox', { name: 'Protocol' })).toBeDisabled(); await user.click(screen.getByRole('tab', { name: 'Security' })); const security = screen.getByRole('radiogroup', { name: 'Security' }); expect( within(security) .getAllByRole('radio') .map((radio) => (radio as HTMLInputElement).value), ).toEqual(['none', 'tls', 'reality']); }); it('preserves edit serialization after a keyboard-accessible transport change', async () => { const post = vi.mocked(HttpUtil.post).mockClear(); const user = renderInbound('edit'); await user.click(screen.getByRole('tab', { name: 'Stream' })); const transport = screen.getByRole('combobox', { name: 'Transmission' }); await user.selectOptions(transport, within(transport).getByRole('option', { name: 'gRPC' })); await user.click(screen.getByRole('button', { name: 'Save Changes' })); await waitFor(() => expect(post).toHaveBeenCalledWith('/panel/api/inbounds/update/41', expect.anything()), ); const payload = post.mock.calls.find( ([url]) => url === '/panel/api/inbounds/update/41', )![1] as Record; expect(payload).toMatchObject({ protocol: 'vless', port: 14443, enable: true }); expect(JSON.parse(String(payload.streamSettings))).toMatchObject({ network: 'grpc', security: 'none', grpcSettings: { serviceName: '' }, }); expect(JSON.parse(String(payload.settings))).not.toHaveProperty('clients'); }); it('reveals and describes a validation error on a hidden tab without submitting', async () => { const post = vi.mocked(HttpUtil.post).mockClear(); const user = renderInbound(); const strategy = screen.getByRole('combobox', { name: 'Share address strategy' }); await user.selectOptions(strategy, within(strategy).getByRole('option', { name: 'Custom' })); const address = screen.getByRole('textbox', { name: 'Custom share address' }); await user.type(address, 'https://example.test/path'); await user.click(screen.getByRole('tab', { name: 'Stream' })); await user.click(screen.getByRole('button', { name: 'Create' })); await waitFor(() => expect(address).toBeVisible()); expect(address).toHaveAttribute('aria-invalid', 'true'); expect(address).toHaveAccessibleDescription(expect.stringContaining('without a scheme')); await waitFor(() => expect(address).toHaveFocus()); expect(post.mock.calls.filter(([url]) => url === '/panel/api/inbounds/add')).toEqual([]); }); });