fix(frontend): map outbound mobile-card actions through the real index too

The desktop outbounds table was keyed by the outbound's real index, but the
mobile card list was left keying the probe trigger and every test-state
lookup by the positional row index. With a hidden balancer-loopback outbound
present, tapping Check on a mobile card probed the wrong outbound and the
Test-All results landed on the wrong card. Key onTest and the
testResult/isTesting reads by record.key, matching the desktop columns.
This commit is contained in:
MHSanaei
2026-07-15 06:29:49 +02:00
parent 0061892d87
commit eb769e3f1f
2 changed files with 44 additions and 9 deletions
@@ -63,8 +63,8 @@ export default function OutboundCardList({
setShowEgressIp((prev) => ({ ...prev, [key]: visible }));
};
const renderEgress = (index: number, rowKey: string) => {
const result = testResult(outboundTestStates, index);
const renderEgress = (testKey: number, rowKey: string) => {
const result = testResult(outboundTestStates, testKey);
const egress = result?.egress;
const isEgressVisible = !!showEgressIp[rowKey];
const flag = countryFlag(egress?.country);
@@ -156,26 +156,26 @@ export default function OutboundCardList({
))}
</div>
)}
{renderEgress(index, String(record.key))}
{renderEgress(record.key, String(record.key))}
<div className="card-foot">
<span className="traffic-up"> {SizeFormatter.sizeFormat(trafficFor(outboundsTraffic, record).up)}</span>
<span className="traffic-sep" />
<span className="traffic-down"> {SizeFormatter.sizeFormat(trafficFor(outboundsTraffic, record).down)}</span>
<span className="card-test">
{testResult(outboundTestStates, index) ? (
<TestResultPopover result={testResult(outboundTestStates, index)!} />
) : isTesting(outboundTestStates, index) ? (
{testResult(outboundTestStates, record.key) ? (
<TestResultPopover result={testResult(outboundTestStates, record.key)!} />
) : isTesting(outboundTestStates, record.key) ? (
<LoadingOutlined />
) : null}
<Button
type="primary"
shape="circle"
size="small"
loading={isTesting(outboundTestStates, index)}
disabled={isUntestable(record) || isTesting(outboundTestStates, index)}
loading={isTesting(outboundTestStates, record.key)}
disabled={isUntestable(record) || isTesting(outboundTestStates, record.key)}
icon={<ThunderboltOutlined />}
aria-label={t('check')}
onClick={() => onTest(index, testMode)}
onClick={() => onTest(record.key, testMode)}
/>
</span>
</div>
@@ -53,4 +53,39 @@ describe('OutboundsTab hidden-loopback index mapping', () => {
expect(onTest).toHaveBeenCalledTimes(1);
expect(onTest.mock.calls[0][0]).toBe(2);
});
it('probes the real array index from the mobile card list as well', () => {
const onTest = vi.fn();
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
renderWithProviders(
<QueryClientProvider client={queryClient}>
<OutboundsTab
templateSettings={settingsWithHiddenLoopback()}
setTemplateSettings={vi.fn()}
outboundsTraffic={[]}
outboundTestStates={{}}
subscriptionTestStates={{}}
testingAll={false}
inboundTags={[]}
isMobile
onResetTraffic={vi.fn()}
onTest={onTest}
onTestSubscription={vi.fn()}
onTestAll={vi.fn()}
onShowWarp={vi.fn()}
onShowNord={vi.fn()}
/>
</QueryClientProvider>,
);
const cards = document.querySelectorAll('.outbound-card');
expect(cards.length).toBe(2);
const checkButton = cards[1].querySelector('button[aria-label="Check"]') as HTMLButtonElement;
fireEvent.click(checkButton);
expect(onTest).toHaveBeenCalledTimes(1);
expect(onTest.mock.calls[0][0]).toBe(2);
});
});