diff --git a/frontend/src/pages/xray/basics/BasicsTab.tsx b/frontend/src/pages/xray/basics/BasicsTab.tsx index 115f031ab..d29a67f79 100644 --- a/frontend/src/pages/xray/basics/BasicsTab.tsx +++ b/frontend/src/pages/xray/basics/BasicsTab.tsx @@ -115,7 +115,8 @@ export default function BasicsTab({ ?.sockopt; const raw = sockopt?.happyEyeballs; if (raw == null || typeof raw !== 'object') return null; - return HappyEyeballsSchema.parse(raw); + const parsed = HappyEyeballsSchema.safeParse(raw); + return parsed.success ? parsed.data : null; })(); const setDirectHappyEyeballs = useCallback( diff --git a/frontend/src/test/basics-happy-eyeballs.test.tsx b/frontend/src/test/basics-happy-eyeballs.test.tsx new file mode 100644 index 000000000..76129b115 --- /dev/null +++ b/frontend/src/test/basics-happy-eyeballs.test.tsx @@ -0,0 +1,36 @@ +import { describe, it, expect, vi } from 'vitest'; + +import BasicsTab from '@/pages/xray/basics/BasicsTab'; +import type { XraySettingsValue } from '@/hooks/useXraySetting'; + +import { renderWithProviders } from './test-utils'; + +function settingsWithMalformedHappyEyeballs(): XraySettingsValue { + return { + outbounds: [ + { + protocol: 'freedom', + tag: 'direct', + streamSettings: { sockopt: { happyEyeballs: { tryDelayMs: 'fast' } } }, + }, + ], + } as unknown as XraySettingsValue; +} + +describe('BasicsTab malformed happyEyeballs', () => { + it('renders instead of white-screening on a wrong-typed happyEyeballs value', () => { + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + expect(() => + renderWithProviders( + , + ), + ).not.toThrow(); + errorSpy.mockRestore(); + }); +});