diff --git a/frontend/src/pages/xray/routing/RuleFormModal.tsx b/frontend/src/pages/xray/routing/RuleFormModal.tsx index 849678dbb..f214305cc 100644 --- a/frontend/src/pages/xray/routing/RuleFormModal.tsx +++ b/frontend/src/pages/xray/routing/RuleFormModal.tsx @@ -126,7 +126,13 @@ export default function RuleFormModal({ outboundTag: v.outboundTag === '' ? undefined : v.outboundTag, balancerTag: v.balancerTag === '' ? undefined : v.balancerTag, }; + const managedKeys = new Set(Object.keys(built)); const out: Record = {}; + if (rule) { + for (const [key, value] of Object.entries(rule)) { + if (!managedKeys.has(key) && value !== undefined) out[key] = value; + } + } for (const [k, v] of Object.entries(built)) { if (v == null) continue; if (Array.isArray(v) && v.length === 0) continue; diff --git a/frontend/src/test/rule-form-preserve-fields.test.tsx b/frontend/src/test/rule-form-preserve-fields.test.tsx new file mode 100644 index 000000000..97ce1f508 --- /dev/null +++ b/frontend/src/test/rule-form-preserve-fields.test.tsx @@ -0,0 +1,33 @@ +import { describe, it, expect, vi } from 'vitest'; +import { fireEvent, screen } from '@testing-library/react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +import RuleFormModal from '@/pages/xray/routing/RuleFormModal'; + +import { renderWithProviders } from './test-utils'; + +describe('RuleFormModal edit preserves unsurfaced fields', () => { + it('keeps a field the form does not surface (ruleTag) when saving an edit', () => { + const onConfirm = vi.fn(); + const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }); + + renderWithProviders( + + + , + ); + + fireEvent.click(screen.getByRole('button', { name: 'Save Changes' })); + + expect(onConfirm).toHaveBeenCalledTimes(1); + expect(onConfirm.mock.calls[0][0]).toMatchObject({ ruleTag: 'my-tag' }); + }); +});