outbound-form-modal.test.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import { describe, it, expect, vi } from 'vitest';
  2. import { act, fireEvent } from '@testing-library/react';
  3. import OutboundFormModal from '@/pages/xray/outbounds/OutboundFormModal';
  4. import {
  5. renderWithProviders,
  6. fieldLabels,
  7. listSelectOptions,
  8. chooseSelectOption,
  9. } from './test-utils';
  10. function renderModal(outbound: Record<string, unknown> | null = null) {
  11. return renderWithProviders(
  12. <OutboundFormModal
  13. open
  14. outbound={outbound}
  15. existingTags={[]}
  16. onClose={() => {}}
  17. onConfirm={() => {}}
  18. />,
  19. );
  20. }
  21. describe('OutboundFormModal', () => {
  22. it('renders add mode without crashing', () => {
  23. renderModal(null);
  24. expect(document.querySelector('.ant-modal')).toBeTruthy();
  25. expect(fieldLabels().length).toBeGreaterThan(0);
  26. });
  27. it('field structure differs per protocol (not a vacuous snapshot loop)', async () => {
  28. renderModal(null);
  29. const protocols = listSelectOptions('protocol');
  30. expect(protocols.length).toBeGreaterThan(3);
  31. const labelsByProto: Record<string, string[]> = {};
  32. for (const proto of protocols) {
  33. chooseSelectOption('protocol', proto);
  34. // Flush antd Form.useWatch('protocol') so protocol-specific fields render before
  35. // reading; otherwise every iteration sees the same default (vless) DOM.
  36. await act(async () => {
  37. await new Promise((r) => setTimeout(r, 0));
  38. });
  39. labelsByProto[proto] = fieldLabels();
  40. }
  41. // Distinct protocols must yield distinct field sets (a vacuous loop is all-identical).
  42. const distinctShapes = new Set(Object.values(labelsByProto).map((l) => l.join('|')));
  43. expect(distinctShapes.size).toBeGreaterThan(1);
  44. // vless carries an Encryption field; wireguard does not — proves real protocol switching.
  45. if (labelsByProto.vless) {
  46. expect(labelsByProto.vless).toContain('Encryption');
  47. }
  48. if (labelsByProto.wireguard) {
  49. expect(labelsByProto.wireguard).not.toContain('Encryption');
  50. }
  51. }, 30000); // iterates every protocol, re-rendering a heavy modal each time — slow on CI runners
  52. it('saves a vless reverse outbound while reverse sniffing stays disabled', async () => {
  53. const onConfirm = vi.fn();
  54. renderWithProviders(
  55. <OutboundFormModal
  56. open
  57. outbound={{
  58. protocol: 'vless',
  59. tag: 'reverse-out',
  60. settings: {
  61. vnext: [
  62. {
  63. address: 'example.com',
  64. port: 443,
  65. users: [{ id: 'c9f0c2d0-0000-4000-8000-000000000000', encryption: 'none' }],
  66. },
  67. ],
  68. reverse: { tag: 'r1', sniffing: {} },
  69. },
  70. streamSettings: { network: 'tcp', security: 'none' },
  71. }}
  72. existingTags={[]}
  73. onClose={() => {}}
  74. onConfirm={onConfirm}
  75. />,
  76. );
  77. await act(async () => {
  78. await new Promise((r) => setTimeout(r, 0));
  79. });
  80. const ok = document.querySelector('.ant-modal-footer .ant-btn-primary') as HTMLElement;
  81. expect(ok).toBeTruthy();
  82. await act(async () => {
  83. fireEvent.click(ok);
  84. });
  85. await act(async () => {
  86. await new Promise((r) => setTimeout(r, 0));
  87. });
  88. expect(onConfirm).toHaveBeenCalledTimes(1);
  89. const payload = onConfirm.mock.calls[0][0] as {
  90. settings: { reverse?: { tag?: string } };
  91. };
  92. expect(payload.settings.reverse?.tag).toBe('r1');
  93. });
  94. });