diff --git a/frontend/src/lib/xray/forms/fields/SniffingField.tsx b/frontend/src/lib/xray/forms/fields/SniffingField.tsx index aee7308a0..45ce5ad26 100644 --- a/frontend/src/lib/xray/forms/fields/SniffingField.tsx +++ b/frontend/src/lib/xray/forms/fields/SniffingField.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react'; import { Form } from 'antd'; import SniffingFields from '@/lib/xray/forms/SniffingFields'; -import type { Sniffing } from '@/schemas/primitives/sniffing'; +import { SniffingSchema, type Sniffing } from '@/schemas/primitives/sniffing'; interface SniffingFieldProps { value?: Sniffing; @@ -12,12 +12,12 @@ interface SniffingFieldProps { export default function SniffingField({ value, onChange, enableLabel }: SniffingFieldProps) { const [form] = Form.useForm(); - const [initial] = useState(() => value ?? ({} as Sniffing)); + const [initial] = useState(() => value ?? SniffingSchema.parse({})); const onChangeRef = useRef(onChange); onChangeRef.current = onChange; const lastEmitted = useRef(JSON.stringify(initial)); - const sniffing = Form.useWatch('sniffing', form) as Sniffing | undefined; + const sniffing = Form.useWatch('sniffing', { form, preserve: true }) as Sniffing | undefined; useEffect(() => { if (sniffing === undefined) return; diff --git a/frontend/src/test/outbound-form-modal.test.tsx b/frontend/src/test/outbound-form-modal.test.tsx index 6c45a1e85..0405902d3 100644 --- a/frontend/src/test/outbound-form-modal.test.tsx +++ b/frontend/src/test/outbound-form-modal.test.tsx @@ -1,5 +1,5 @@ -import { describe, it, expect } from 'vitest'; -import { act } from '@testing-library/react'; +import { describe, it, expect, vi } from 'vitest'; +import { act, fireEvent } from '@testing-library/react'; import OutboundFormModal from '@/pages/xray/outbounds/OutboundFormModal'; import { @@ -54,4 +54,41 @@ describe('OutboundFormModal', () => { expect(labelsByProto.wireguard).not.toContain('Encryption'); } }, 30000); // iterates every protocol, re-rendering a heavy modal each time — slow on CI runners + + it('saves a vless reverse outbound while reverse sniffing stays disabled', async () => { + const onConfirm = vi.fn(); + renderWithProviders( + {}} + onConfirm={onConfirm} + />, + ); + await act(async () => { await new Promise((r) => setTimeout(r, 0)); }); + + const ok = document.querySelector('.ant-modal-footer .ant-btn-primary') as HTMLElement; + expect(ok).toBeTruthy(); + await act(async () => { fireEvent.click(ok); }); + await act(async () => { await new Promise((r) => setTimeout(r, 0)); }); + + expect(onConfirm).toHaveBeenCalledTimes(1); + const payload = onConfirm.mock.calls[0][0] as { + settings: { reverse?: { tag?: string } }; + }; + expect(payload.settings.reverse?.tag).toBe('r1'); + }); });