|
|
@@ -1,8 +1,9 @@
|
|
|
import { describe, it, expect } from 'vitest';
|
|
|
-import { screen, act } from '@testing-library/react';
|
|
|
+import { screen, act, render, cleanup } from '@testing-library/react';
|
|
|
|
|
|
import InboundFormModal from '@/pages/inbounds/form/InboundFormModal';
|
|
|
import { DBInbound } from '@/models/dbinbound';
|
|
|
+import { ThemeProvider } from '@/hooks/useTheme';
|
|
|
import {
|
|
|
renderWithProviders,
|
|
|
fieldLabels,
|
|
|
@@ -90,4 +91,54 @@ describe('InboundFormModal', () => {
|
|
|
const shareAddrInput = await screen.findByDisplayValue('edge.example.test');
|
|
|
expect((shareAddrInput as HTMLInputElement).value).toBe('edge.example.test');
|
|
|
});
|
|
|
+
|
|
|
+ it('keeps the persisted node share strategy through the nodes-loading race (#5375)', async () => {
|
|
|
+ const node = { id: 1, name: 'arm2', enable: true, status: 'online' } as never;
|
|
|
+ const buildInbound = () => new DBInbound({
|
|
|
+ id: 1,
|
|
|
+ port: 23456,
|
|
|
+ listen: '',
|
|
|
+ protocol: 'vless',
|
|
|
+ remark: 'noded',
|
|
|
+ enable: true,
|
|
|
+ settings: { clients: [] },
|
|
|
+ streamSettings: { network: 'tcp', security: 'none', tcpSettings: {} },
|
|
|
+ sniffing: { enabled: false },
|
|
|
+ nodeId: 1,
|
|
|
+ shareAddrStrategy: 'node',
|
|
|
+ });
|
|
|
+ const flush = async () => { await act(async () => { await new Promise((r) => setTimeout(r, 0)); }); };
|
|
|
+ const strategyItem = (title: string) =>
|
|
|
+ document.querySelector(`.ant-select-content[title="${title}"]`);
|
|
|
+ const modal = (nodes: never[], fetched: boolean) => (
|
|
|
+ <ThemeProvider>
|
|
|
+ <InboundFormModal
|
|
|
+ open
|
|
|
+ mode="edit"
|
|
|
+ dbInbound={buildInbound()}
|
|
|
+ dbInbounds={[]}
|
|
|
+ availableNodes={nodes}
|
|
|
+ availableNodesFetched={fetched}
|
|
|
+ onClose={() => {}}
|
|
|
+ onSaved={() => {}}
|
|
|
+ />
|
|
|
+ </ThemeProvider>
|
|
|
+ );
|
|
|
+
|
|
|
+ // Baseline: nodes already loaded, so the node option is offered and selected.
|
|
|
+ render(modal([node], true));
|
|
|
+ await flush();
|
|
|
+ expect(strategyItem('Node address')).toBeTruthy();
|
|
|
+ cleanup();
|
|
|
+
|
|
|
+ // Race: the modal mounts before /nodes/list resolves (empty placeholder),
|
|
|
+ // then nodes arrive. The persisted 'node' strategy must survive the gap and
|
|
|
+ // stay selected once the option reappears — not silently revert to listen.
|
|
|
+ const { rerender } = render(modal([], false));
|
|
|
+ await flush();
|
|
|
+ rerender(modal([node], true));
|
|
|
+ await flush();
|
|
|
+ expect(strategyItem('Node address')).toBeTruthy();
|
|
|
+ expect(strategyItem('Inbound listen')).toBeFalsy();
|
|
|
+ });
|
|
|
});
|