mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-06 14:12:09 +03:00
fix(inbounds): list clients in the detach/attach modals when they mount open (#6736)
* fix(inbounds): list clients in the detach modal when it mounts open DetachClientsModal seeded its synced-source state with the open source, so a modal first mounted with open and a source saw no change on its first render and never read the client rows. The table stayed empty until the modal was closed and reopened. Seed the state with null so the first open render loads the rows, as the reset-during-render check already expects. The test mounts the modal already open and fails without this change. Fixes #6733 * fix(inbounds): list clients in the attach modal when it mounts open AttachClientsModal seeds its synced-source state the same way DetachClientsModal did, so a modal first mounted with open and a source saw no change on its first render and never read the client rows. The table stayed empty, and nothing was pre-selected, until the modal was closed and reopened. Seed the state with null here too. The test mounts the attach modal already open and fails without this change. Refs #6733 --------- Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
@@ -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<DBInbound | null>(null);
|
||||
if (openSource !== syncedSource) {
|
||||
setSyncedSource(openSource);
|
||||
if (openSource) {
|
||||
|
||||
@@ -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<DBInbound | null>(null);
|
||||
if (openSource !== syncedSource) {
|
||||
setSyncedSource(openSource);
|
||||
if (openSource) {
|
||||
|
||||
@@ -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(
|
||||
<AttachClientsModal open source={sourceInbound()} dbInbounds={[]} onClose={() => {}} />,
|
||||
);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -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(<DetachClientsModal open source={sourceInbound()} onClose={() => {}} />);
|
||||
|
||||
expect(await screen.findByText('alice@test')).toBeTruthy();
|
||||
expect(screen.getByText('bob@test')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user