client-form-flow.test.tsx 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. import { describe, it, expect, vi } from 'vitest';
  2. import { render, fireEvent, waitFor, screen } from '@testing-library/react';
  3. import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
  4. import { ThemeProvider } from '@/hooks/useTheme';
  5. import ClientFormModal from '@/pages/clients/ClientFormModal';
  6. import type { ClientRecord, InboundOption } from '@/hooks/useClients';
  7. function makeQC() {
  8. return new QueryClient({ defaultOptions: { queries: { retry: false } } });
  9. }
  10. const REALITY_INBOUND = {
  11. id: 4,
  12. port: 10443,
  13. protocol: 'vless',
  14. tag: 'in-10443-tcp',
  15. tlsFlowCapable: true,
  16. enable: true,
  17. } as unknown as InboundOption;
  18. const CLIENT = {
  19. email: 'testuser',
  20. flow: 'xtls-rprx-vision',
  21. uuid: '11111111-1111-1111-1111-111111111111',
  22. subId: 'subid123',
  23. enable: true,
  24. } as unknown as ClientRecord;
  25. function savedFlow(save: ReturnType<typeof vi.fn>): unknown {
  26. return (save.mock.calls[0][0] as Record<string, unknown>).flow;
  27. }
  28. describe('ClientFormModal — Vision flow preservation', () => {
  29. it('keeps xtls-rprx-vision with a stable Reality inbound', async () => {
  30. const qc = makeQC();
  31. const save = vi.fn().mockResolvedValue({ success: true });
  32. render(
  33. <ThemeProvider>
  34. <QueryClientProvider client={qc}>
  35. <ClientFormModal open mode="edit" client={CLIENT} inbounds={[REALITY_INBOUND]} attachedIds={[4]} save={save} onOpenChange={() => {}} />
  36. </QueryClientProvider>
  37. </ThemeProvider>,
  38. );
  39. fireEvent.click(await screen.findByRole('button', { name: /save/i }));
  40. await waitFor(() => expect(save).toHaveBeenCalled());
  41. expect(savedFlow(save)).toBe('xtls-rprx-vision');
  42. });
  43. it('does not drop a selected Vision flow while the inbound options momentarily reload', async () => {
  44. const qc = makeQC();
  45. const save = vi.fn().mockResolvedValue({ success: true });
  46. const tree = (inbounds: InboundOption[]) => (
  47. <ThemeProvider>
  48. <QueryClientProvider client={qc}>
  49. <ClientFormModal open mode="edit" client={CLIENT} inbounds={inbounds} attachedIds={[4]} save={save} onOpenChange={() => {}} />
  50. </QueryClientProvider>
  51. </ThemeProvider>
  52. );
  53. // Options loaded -> reloading (inboundOptionsQuery.data ?? [] === []) -> loaded again.
  54. const { rerender } = render(tree([REALITY_INBOUND]));
  55. await screen.findByRole('button', { name: /save/i });
  56. rerender(tree([]));
  57. rerender(tree([REALITY_INBOUND]));
  58. fireEvent.click(await screen.findByRole('button', { name: /save/i }));
  59. await waitFor(() => expect(save).toHaveBeenCalled());
  60. expect(savedFlow(save)).toBe('xtls-rprx-vision');
  61. });
  62. });