diff --git a/frontend/src/pages/inbounds/clients/AttachClientsModal.tsx b/frontend/src/pages/inbounds/clients/AttachClientsModal.tsx index db546a93b..6c0cc893d 100644 --- a/frontend/src/pages/inbounds/clients/AttachClientsModal.tsx +++ b/frontend/src/pages/inbounds/clients/AttachClientsModal.tsx @@ -60,7 +60,7 @@ export default function AttachClientsModal({ // React resets this during render rather than in an effect so the modal's // first open frame already shows cleared fields. const openSource = open ? source : null; - const [syncedSource, setSyncedSource] = useState(openSource); + const [syncedSource, setSyncedSource] = useState(null); if (openSource !== syncedSource) { setSyncedSource(openSource); if (openSource) { diff --git a/frontend/src/pages/inbounds/clients/DetachClientsModal.tsx b/frontend/src/pages/inbounds/clients/DetachClientsModal.tsx index 7dee9625f..bbf3b5e19 100644 --- a/frontend/src/pages/inbounds/clients/DetachClientsModal.tsx +++ b/frontend/src/pages/inbounds/clients/DetachClientsModal.tsx @@ -54,7 +54,7 @@ export default function DetachClientsModal({ // Reset during render, not in an effect, so the first frame is already clean. const openSource = open ? source : null; - const [syncedSource, setSyncedSource] = useState(openSource); + const [syncedSource, setSyncedSource] = useState(null); if (openSource !== syncedSource) { setSyncedSource(openSource); if (openSource) { diff --git a/frontend/src/test/attach-clients-modal.test.tsx b/frontend/src/test/attach-clients-modal.test.tsx new file mode 100644 index 000000000..c7aa18b12 --- /dev/null +++ b/frontend/src/test/attach-clients-modal.test.tsx @@ -0,0 +1,43 @@ +import { describe, expect, it } from 'vitest'; +import { screen } from '@testing-library/react'; + +import AttachClientsModal from '@/pages/inbounds/clients/AttachClientsModal'; +import { DBInbound } from '@/models/dbinbound'; + +import { renderWithProviders } from './test-utils'; + +function sourceInbound() { + return new DBInbound({ + id: 7, + port: 443, + listen: '', + protocol: 'vless', + remark: 'edge', + enable: true, + settings: JSON.stringify({ + clients: [ + { id: 'uuid-1', email: 'alice@test' }, + { id: 'uuid-2', email: 'bob@test' }, + ], + decryption: 'none', + }), + streamSettings: JSON.stringify({ network: 'tcp', security: 'none' }), + sniffing: '', + }); +} + +describe('AttachClientsModal', () => { + it('lists the source clients, selected, when first mounted already open', async () => { + renderWithProviders( + {}} />, + ); + + expect(await screen.findByText('alice@test')).toBeTruthy(); + expect(screen.getByText('bob@test')).toBeTruthy(); + + const boxes = screen.getAllByRole('checkbox') as HTMLInputElement[]; + const rowBoxes = boxes.slice(1); + expect(rowBoxes).toHaveLength(2); + expect(rowBoxes.every((b) => b.checked)).toBe(true); + }); +}); diff --git a/frontend/src/test/detach-clients-modal.test.tsx b/frontend/src/test/detach-clients-modal.test.tsx new file mode 100644 index 000000000..89d3352bb --- /dev/null +++ b/frontend/src/test/detach-clients-modal.test.tsx @@ -0,0 +1,36 @@ +import { describe, expect, it } from 'vitest'; +import { screen } from '@testing-library/react'; + +import DetachClientsModal from '@/pages/inbounds/clients/DetachClientsModal'; +import { DBInbound } from '@/models/dbinbound'; + +import { renderWithProviders } from './test-utils'; + +function sourceInbound() { + return new DBInbound({ + id: 7, + port: 443, + listen: '', + protocol: 'vless', + remark: 'edge', + enable: true, + settings: JSON.stringify({ + clients: [ + { id: 'uuid-1', email: 'alice@test' }, + { id: 'uuid-2', email: 'bob@test' }, + ], + decryption: 'none', + }), + streamSettings: JSON.stringify({ network: 'tcp', security: 'none' }), + sniffing: '', + }); +} + +describe('DetachClientsModal', () => { + it('lists the attached clients when first mounted already open', async () => { + renderWithProviders( {}} />); + + expect(await screen.findByText('alice@test')).toBeTruthy(); + expect(screen.getByText('bob@test')).toBeTruthy(); + }); +});