fix: tighten ci typecheck coverage

This commit is contained in:
Cita
2026-03-23 16:02:13 +08:00
parent 40c8c1c7ad
commit 4b903fdd3b
69 changed files with 335 additions and 222 deletions
+1 -1
View File
@@ -155,7 +155,7 @@ describe('App mobile layout', () => {
'uses the shared breakpoint at width $width',
async ({ width, expectedLayout, hasHamburger }) => {
setupRuntime(width);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
+1 -1
View File
@@ -151,7 +151,7 @@ describe('App mobile sidebar', () => {
it('opens the mobile drawer from the hamburger trigger and exposes the close affordance', async () => {
setupRuntime(768);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -42,7 +42,7 @@ describe('ModelAnalysisPanel token summaries', () => {
});
it('renders total token summaries with compact units', () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
act(() => {
root = create(
@@ -70,10 +70,10 @@ describe('ModelAnalysisPanel token summaries', () => {
globalThis.document = {
documentElement: {},
} as unknown as Document;
delete (globalThis as typeof globalThis & { getComputedStyle?: typeof getComputedStyle }).getComputedStyle;
delete (globalThis as typeof globalThis & { MutationObserver?: typeof MutationObserver }).MutationObserver;
Reflect.deleteProperty(globalThis as typeof globalThis & Record<string, unknown>, 'getComputedStyle');
Reflect.deleteProperty(globalThis as typeof globalThis & Record<string, unknown>, 'MutationObserver');
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
await expect(act(async () => {
root = create(
+1 -1
View File
@@ -7,7 +7,7 @@ import CenteredModal from './CenteredModal.js';
describe('CenteredModal component', () => {
it('does not close on backdrop click by default and exposes an explicit close button', async () => {
const onClose = vi.fn();
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -17,8 +17,8 @@ describe('SiteDistributionChart', () => {
getAttribute: vi.fn(),
},
} as unknown as Document;
delete (globalThis as typeof globalThis & { getComputedStyle?: typeof getComputedStyle }).getComputedStyle;
delete (globalThis as typeof globalThis & { MutationObserver?: typeof MutationObserver }).MutationObserver;
Reflect.deleteProperty(globalThis as typeof globalThis & Record<string, unknown>, 'getComputedStyle');
Reflect.deleteProperty(globalThis as typeof globalThis & Record<string, unknown>, 'MutationObserver');
});
afterEach(() => {
@@ -28,7 +28,7 @@ describe('SiteDistributionChart', () => {
});
it('renders with fallback label color when browser theme APIs are unavailable', async () => {
let renderer: ReturnType<typeof create> | null = null;
let renderer!: WebTestRenderer;
await expect(act(async () => {
renderer = create(
@@ -46,6 +46,6 @@ describe('SiteDistributionChart', () => {
);
})).resolves.toBeUndefined();
renderer?.unmount();
renderer.unmount();
});
});
@@ -17,6 +17,10 @@ interface SiteDistributionChartProps {
type ViewMode = 'balance' | 'spend';
function coerceDatumRecord(datum: unknown): Record<string, unknown> {
return datum && typeof datum === 'object' ? datum as Record<string, unknown> : {};
}
function safeNumber(value: unknown): number {
if (typeof value !== 'number' || Number.isNaN(value) || !Number.isFinite(value)) return 0;
return value;
@@ -120,24 +124,32 @@ export default function SiteDistributionChart({ data, loading }: SiteDistributio
mark: {
content: [
{
key: (datum: Record<string, unknown>) => datum.siteName as string,
value: (datum: Record<string, unknown>) => {
const val = safeNumber(datum.value);
key: (datum: unknown) => {
const item = coerceDatumRecord(datum);
return String(item.siteName || '-');
},
value: (datum: unknown) => {
const item = coerceDatumRecord(datum);
const val = safeNumber(item.value);
return `$${val.toFixed(2)}`;
},
},
{
key: '占比',
value: (datum: Record<string, unknown>) => {
const pct = datum._percent_ as number;
value: (datum: unknown) => {
const item = coerceDatumRecord(datum);
const pct = safeNumber(item._percent_);
return `${safeNumber(pct).toFixed(1)}%`;
},
},
{
key: '账户数',
value: (datum: Record<string, unknown>) => `${datum.accountCount}`,
value: (datum: unknown) => {
const item = coerceDatumRecord(datum);
return String(item.accountCount || 0);
},
},
],
] as any,
},
},
color: PIE_COLORS,
+1 -1
View File
@@ -11,7 +11,7 @@ vi.mock('react-dom', () => ({
describe('MobileDrawer', () => {
it('renders content, locks body scroll, and exposes explicit close affordances', async () => {
const onClose = vi.fn();
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
vi.stubGlobal('document', {
body: {
style: {
@@ -92,7 +92,7 @@ describe('SearchModal results', () => {
});
const onClose = vi.fn();
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
+7 -7
View File
@@ -22,7 +22,7 @@ describe('useIsMobile', () => {
removeEventListener: (event: string, handler: () => void) => {
listeners.get(event)?.delete(handler);
},
dispatchEvent: (event: { type: string }) => {
dispatchEvent: (event: Event) => {
listeners.get(event.type)?.forEach((handler) => handler());
return true;
},
@@ -39,7 +39,7 @@ describe('useIsMobile', () => {
innerWidth: 767,
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -66,7 +66,7 @@ describe('useIsMobile', () => {
}),
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -84,7 +84,7 @@ describe('useIsMobile', () => {
});
it('treats 767 and 768 as mobile, but 769 as desktop', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -97,7 +97,7 @@ describe('useIsMobile', () => {
widthRef.current = 767;
window.innerWidth = 767;
await act(async () => {
window.dispatchEvent({ type: 'resize' });
window.dispatchEvent(new Event('resize'));
});
expect(root.root.findByType('div').props['data-mobile']).toBe('true');
@@ -105,7 +105,7 @@ describe('useIsMobile', () => {
widthRef.current = 768;
window.innerWidth = 768;
await act(async () => {
window.dispatchEvent({ type: 'resize' });
window.dispatchEvent(new Event('resize'));
});
expect(root.root.findByType('div').props['data-mobile']).toBe('true');
@@ -113,7 +113,7 @@ describe('useIsMobile', () => {
widthRef.current = 769;
window.innerWidth = 769;
await act(async () => {
window.dispatchEvent({ type: 'resize' });
window.dispatchEvent(new Event('resize'));
});
expect(root.root.findByType('div').props['data-mobile']).toBe('false');
+1 -1
View File
@@ -6,7 +6,7 @@ import './index.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<BrowserRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
+1 -1
View File
@@ -190,7 +190,7 @@ describe('DownstreamKeys mobile layout', () => {
});
it('renders mobile cards and supports select-all-visible batch actions', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
+12 -12
View File
@@ -165,7 +165,7 @@ afterEach(() => {
describe('DownstreamKeys page', () => {
it('loads management data and renders merged row content', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -193,7 +193,7 @@ describe('DownstreamKeys page', () => {
expect(text).toContain('主分组');
expect(text).toContain('移动端');
} finally {
root?.unmount();
root.unmount();
}
});
@@ -213,7 +213,7 @@ describe('DownstreamKeys page', () => {
],
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -236,7 +236,7 @@ describe('DownstreamKeys page', () => {
const select = root!.root.findAllByType('select').find((node) => collectText(node).includes('仅禁用'));
await act(async () => {
select.props.onChange({ target: { value: 'disabled' } });
select!.props.onChange({ target: { value: 'disabled' } });
});
await flushMicrotasks();
expect(collectText(root!.root)).toContain('batch-key');
@@ -247,7 +247,7 @@ describe('DownstreamKeys page', () => {
});
it('supports create flow and drawer trend loading', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -327,7 +327,7 @@ describe('DownstreamKeys page', () => {
],
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -415,7 +415,7 @@ describe('DownstreamKeys page', () => {
})
.mockImplementationOnce(() => new Promise(() => {}));
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -447,12 +447,12 @@ describe('DownstreamKeys page', () => {
expect(collectText(root!.root)).not.toContain('固定窗口对比');
expect(collectText(root!.root)).not.toContain('trend:2');
} finally {
root?.unmount();
root.unmount();
}
});
it('separates exact models from group routes in advanced config and uses single-column layout', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -503,7 +503,7 @@ describe('DownstreamKeys page', () => {
{ id: 14, modelPattern: 'claude-opus-4-6', displayName: 'Claude Opus 4.6', enabled: true },
]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -570,7 +570,7 @@ describe('DownstreamKeys page', () => {
});
it('uses backend batch api for selected rows', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -606,7 +606,7 @@ describe('DownstreamKeys page', () => {
});
it('supports group and tag editing plus batch metadata update', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
+8 -8
View File
@@ -283,7 +283,7 @@ describe('ImportExport', () => {
});
it('shows ALL-API-Hub V2 preview counts and ignored sections', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -309,7 +309,7 @@ describe('ImportExport', () => {
});
it('does not label native metapi backups as ALL-API-Hub V2', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -335,7 +335,7 @@ describe('ImportExport', () => {
it('uses backend import summary in the completion toast', async () => {
const confirmSpy = vi.mocked((window as { confirm: (...args: unknown[]) => boolean }).confirm);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -385,7 +385,7 @@ describe('ImportExport', () => {
});
it('loads webdav config and saves updates from the import/export page', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -422,7 +422,7 @@ describe('ImportExport', () => {
});
it('shows v2.1 config-backup wording and local-state notice', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -445,7 +445,7 @@ describe('ImportExport', () => {
});
it('renders webdav export type with ModernSelect instead of a native select', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -493,7 +493,7 @@ describe('ImportExport', () => {
});
it('disables webdav actions while config has unsaved changes', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -552,7 +552,7 @@ describe('ImportExport', () => {
},
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
+8 -6
View File
@@ -41,12 +41,14 @@ import {
type DebugTab,
type ModelTesterInputs,
type ModelTesterModeState,
type ParameterEnabled,
type PlaygroundMode,
type PlaygroundMultipartFile,
type TestTargetFormat,
type TestChatPayload,
} from './helpers/modelTesterSession.js';
type ParameterEnabled,
type PlaygroundMode,
type PlaygroundProtocol,
type PlaygroundMultipartFile,
type ProxyTestEnvelope,
type TestTargetFormat,
type TestChatPayload,
} from './helpers/modelTesterSession.js';
import {
buildConversationFileAccept,
buildConversationFileHint,
@@ -82,7 +82,7 @@ describe('Models marketplace text', () => {
});
it('renders readable Chinese labels and fallback descriptions for marketplace models', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -172,7 +172,7 @@ describe('Models marketplace text', () => {
],
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -213,7 +213,7 @@ describe('Models marketplace text', () => {
globalThis.window = nextWindow;
globalThis.matchMedia = nextWindow.matchMedia;
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -241,7 +241,7 @@ describe('Models marketplace text', () => {
} as unknown as Window & typeof globalThis;
apiMock.getModelsMarketplace.mockImplementation(() => new Promise(() => {}));
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -331,7 +331,7 @@ describe('Models marketplace text', () => {
],
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -470,7 +470,7 @@ describe('Models marketplace text', () => {
],
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -547,7 +547,7 @@ describe('Models marketplace text', () => {
],
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
+2 -2
View File
@@ -68,7 +68,7 @@ describe('NotificationSettings', () => {
});
it('loads and saves telegram api base url and topic id', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -139,7 +139,7 @@ describe('NotificationSettings', () => {
notifyCooldownSec: 300,
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
+9 -9
View File
@@ -108,7 +108,7 @@ describe('OAuthManagement page', () => {
offset: 0,
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -206,7 +206,7 @@ describe('OAuthManagement page', () => {
accountId: 7,
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -304,7 +304,7 @@ describe('OAuthManagement page', () => {
});
apiMock.submitOAuthManualCallback.mockResolvedValue({ success: true });
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -398,7 +398,7 @@ describe('OAuthManagement page', () => {
offset: 0,
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -464,7 +464,7 @@ describe('OAuthManagement page', () => {
offset: 0,
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -526,7 +526,7 @@ describe('OAuthManagement page', () => {
status: 'pending',
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -618,7 +618,7 @@ describe('OAuthManagement page', () => {
status: 'pending',
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -717,7 +717,7 @@ describe('OAuthManagement page', () => {
.mockResolvedValueOnce({ items: [], total: 0, limit: 100, offset: 0 });
apiMock.deleteOAuthConnection.mockResolvedValue({ success: true });
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -860,7 +860,7 @@ describe('OAuthManagement page', () => {
},
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
+2 -2
View File
@@ -54,7 +54,7 @@ describe('ProgramLogs status label', () => {
},
]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
await act(async () => {
root = create(
<MemoryRouter initialEntries={['/events']}>
@@ -90,7 +90,7 @@ describe('ProgramLogs status label', () => {
},
]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
await act(async () => {
root = create(
<MemoryRouter initialEntries={['/events']}>
@@ -158,7 +158,7 @@ describe('ProxyLogs server-driven page', () => {
});
it('requests paginated data from the server and renders server summary counts', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -194,7 +194,7 @@ describe('ProxyLogs server-driven page', () => {
});
it('keeps the model badge sized to the model name in desktop rows', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -250,7 +250,7 @@ describe('ProxyLogs server-driven page', () => {
],
}));
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -277,7 +277,7 @@ describe('ProxyLogs server-driven page', () => {
});
it('re-queries the server for status, client, and search changes instead of filtering locally', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -342,7 +342,7 @@ describe('ProxyLogs server-driven page', () => {
});
it('loads detail on first expand and reuses the cached detail on re-expand', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -385,7 +385,7 @@ describe('ProxyLogs server-driven page', () => {
});
it('hydrates site and time filters from the route query', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
+14 -5
View File
@@ -34,6 +34,12 @@ type ProxyLogDetailState = {
error?: string;
};
type ProxyLogSiteFilterOption = {
id: number;
name: string;
status: string | null;
};
const PAGE_SIZES = [20, 50, 100];
const DEFAULT_PAGE_SIZE = 50;
const PROXY_LOG_CLIENT_FAMILY_LABELS: Record<string, string> = {
@@ -353,14 +359,17 @@ export default function ProxyLogs() {
try {
const result = await api.getSites();
const rows = Array.isArray(result) ? result : (result?.sites || []);
const normalized = rows
const normalized: ProxyLogSiteFilterOption[] = rows
.map((site: any) => ({
id: Number(site?.id || 0),
name: String(site?.name || '').trim() || `站点 #${site?.id ?? ''}`,
status: typeof site?.status === 'string' ? site.status : null,
}))
.filter((site) => site.id > 0)
.sort((left, right) => left.name.localeCompare(right.name, 'zh-CN'));
.filter((site: ProxyLogSiteFilterOption) => site.id > 0)
.sort(
(left: ProxyLogSiteFilterOption, right: ProxyLogSiteFilterOption) =>
left.name.localeCompare(right.name, 'zh-CN'),
);
if (!cancelled) setSites(normalized);
} catch (error) {
console.error('Failed to load sites for proxy log filters:', error);
@@ -701,7 +710,7 @@ export default function ProxyLogs() {
const detailState = detailById[log.id];
const detail = detailState?.data;
const detailLog: ProxyLogRenderItem = detail ? { ...log, ...detail } : log;
const pathMeta = parseProxyLogPathMeta(detailLog.errorMessage);
const pathMeta = parseProxyLogPathMeta(detailLog.errorMessage ?? undefined);
const billingDetailSummary = detail ? formatBillingDetailSummary(detailLog) : null;
const billingProcessLines = detail ? buildBillingProcessLines(detailLog) : [];
const downstreamKeySummary = renderDownstreamKeySummary(detailLog);
@@ -808,7 +817,7 @@ export default function ProxyLogs() {
const detailState = detailById[log.id];
const detail = detailState?.data;
const detailLog: ProxyLogRenderItem = detail ? { ...log, ...detail } : log;
const pathMeta = parseProxyLogPathMeta(detailLog.errorMessage);
const pathMeta = parseProxyLogPathMeta(detailLog.errorMessage ?? undefined);
const billingDetailSummary = detail ? formatBillingDetailSummary(detailLog) : null;
const billingProcessLines = detail ? buildBillingProcessLines(detailLog) : [];
const downstreamKeySummary = renderDownstreamKeySummary(detailLog);
+1 -1
View File
@@ -43,7 +43,7 @@ export default function SiteAnnouncements() {
const [serverTimeZone, setServerTimeZone] = useState<string | undefined>(undefined);
const [highlightAnnouncementId, setHighlightAnnouncementId] = useState<number | null>(null);
const rowRefs = useRef<Map<number, HTMLDivElement>>(new Map());
const highlightTimerRef = useRef<number | null>(null);
const highlightTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const viewerTimeZone = useMemo(() => readClientTimeZone(), []);
const displayTimeZone = resolveSiteAnnouncementTimeZone(viewerTimeZone, serverTimeZone);
+2 -1
View File
@@ -238,7 +238,8 @@ export default function TokenRoutes() {
if (candidatesPromiseRef.current && !force) return;
const seq = ++candidatesSeqRef.current;
candidatesLoadedRef.current = true;
const promise = (async () => {
let promise!: Promise<void>;
promise = (async () => {
try {
const candidateRows = await api.getModelTokenCandidates();
if (candidatesSeqRef.current !== seq) return; // stale
+17 -3
View File
@@ -44,6 +44,20 @@ type AccountTokenSyncResult = {
};
};
type SyncableAccount = {
id: number;
username?: string | null;
accessToken?: string | null;
status?: string | null;
credentialMode?: string | null;
capabilities?: {
proxyOnly?: boolean;
} | null;
site?: {
status?: string | null;
name?: string | null;
} | null;
};
const isAccountSyncable = (account: any) =>
resolveAccountCredentialMode(account) === 'session'
&& account?.status === 'active'
@@ -162,11 +176,11 @@ export function TokensPanel({ embedded = false, onEmbeddedActionsChange }: Token
const nextTokens = tokenRows || [];
setTokens(nextTokens);
setSelectedTokenIds((current) => current.filter((id) => nextTokens.some((token: any) => token.id === id)));
const latestAccounts = accountRows || [];
const latestAccounts: SyncableAccount[] = Array.isArray(accountRows) ? accountRows : [];
setAccounts(latestAccounts);
const syncableAccounts = latestAccounts.filter(isAccountSyncable);
const hasCurrentSelected = syncableAccounts.some((account) => account.id === syncingAccountId);
const hasCurrentSelected = syncableAccounts.some((account: SyncableAccount) => account.id === syncingAccountId);
if (!hasCurrentSelected) {
setSyncingAccountId(syncableAccounts[0]?.id || 0);
}
@@ -209,7 +223,7 @@ export function TokensPanel({ embedded = false, onEmbeddedActionsChange }: Token
api.getAccountTokenGroups(form.accountId)
.then((res: any) => {
if (cancelled) return;
const groups = Array.isArray(res?.groups)
const groups: string[] = Array.isArray(res?.groups)
? res.groups.map((item: any) => String(item || '').trim()).filter(Boolean)
: [];
const normalized = Array.from(new Set(groups));
@@ -61,7 +61,7 @@ describe('Accounts batch actions', () => {
});
it('refreshes balance for selected accounts through the batch toolbar', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -97,7 +97,7 @@ describe('Accounts batch actions', () => {
});
it('selects an account when clicking the row instead of only the checkbox', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -147,7 +147,7 @@ describe('Accounts batch actions', () => {
},
]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -31,7 +31,7 @@ async function renderAccounts(initialEntry: string) {
]);
apiMock.getAccountTokens.mockResolvedValue([]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
await act(async () => {
root = create(
<MemoryRouter initialEntries={[initialEntry]}>
+4 -4
View File
@@ -94,7 +94,7 @@ describe('Accounts edit panel', () => {
});
it('opens edit panel from account row action', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -140,7 +140,7 @@ describe('Accounts edit panel', () => {
disabledCount: 0,
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -205,7 +205,7 @@ describe('Accounts edit panel', () => {
return Promise.resolve({ siteId: accountId, siteName: 'Unknown', models: [] });
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -269,7 +269,7 @@ describe('Accounts edit panel', () => {
});
apiMock.rebuildRoutes.mockRejectedValue(new Error('rebuild failed'));
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -80,7 +80,7 @@ describe('Accounts mobile actions', () => {
});
it('supports select-all-visible from the mobile toolbar', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -115,7 +115,7 @@ describe('Accounts mobile actions', () => {
});
it('clears only the visible segment selection when toggling mobile select-all off', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
apiMock.getAccounts.mockResolvedValue([
{
@@ -62,7 +62,7 @@ describe('Accounts proxy-only expired state', () => {
]);
apiMock.getSites.mockResolvedValue([{ id: 10, name: '小呆api', platform: 'new-api', status: 'active' }]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -58,7 +58,7 @@ describe('Accounts rebind modal', () => {
]);
apiMock.getSites.mockResolvedValue([{ id: 10, name: 'Demo Site', platform: 'new-api', status: 'active' }]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -75,7 +75,7 @@ describe('Accounts refresh action', () => {
apiMock.getSites.mockResolvedValue([{ id: 10, name: 'Demo Site', platform: 'new-api', status: 'active' }]);
apiMock.refreshBalance.mockRejectedValueOnce(new Error('无权进行此操作,access token 无效'));
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -67,7 +67,7 @@ describe('Accounts segmented connections view', () => {
]);
apiMock.getAccountTokens.mockResolvedValue([]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -116,7 +116,7 @@ describe('Accounts segmented connections view', () => {
]);
apiMock.getAccountTokens.mockResolvedValue([]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -59,7 +59,7 @@ describe('Accounts tokens embedded header', () => {
]);
apiMock.getAccountTokens.mockResolvedValue([]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -53,7 +53,7 @@ describe('Accounts verify feedback', () => {
]);
apiMock.verifyToken.mockRejectedValueOnce(new Error('Failed to fetch'));
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -74,7 +74,7 @@ describe('Dashboard performance stat card', () => {
modelAnalysis: null,
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -107,14 +107,14 @@ describe('Dashboard performance stat card', () => {
});
it('shows five skeleton stat cards while dashboard data is still loading', async () => {
let resolveDashboard: ((value: Record<string, unknown>) => void) | null = null;
let resolveDashboard: ((value: Record<string, unknown>) => void) | undefined;
apiMock.getDashboard.mockImplementation(() => (
new Promise((resolve) => {
resolveDashboard = resolve as (value: Record<string, unknown>) => void;
})
));
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -133,18 +133,20 @@ describe('Dashboard performance stat card', () => {
expect(statCards).toHaveLength(5);
} finally {
resolveDashboard?.({
totalBalance: 0,
totalUsed: 0,
todaySpend: 0,
todayReward: 0,
activeAccounts: 0,
totalAccounts: 0,
todayCheckin: { success: 0, total: 0 },
proxy24h: { success: 0, total: 0, totalTokens: 0 },
performance: { windowSeconds: 60, requestsPerMinute: 0, tokensPerMinute: 0 },
modelAnalysis: null,
});
if (resolveDashboard) {
resolveDashboard({
totalBalance: 0,
totalUsed: 0,
todaySpend: 0,
todayReward: 0,
activeAccounts: 0,
totalAccounts: 0,
todayCheckin: { success: 0, total: 0 },
proxy24h: { success: 0, total: 0, totalTokens: 0 },
performance: { windowSeconds: 60, requestsPerMinute: 0, tokensPerMinute: 0 },
modelAnalysis: null,
});
}
root?.unmount();
}
});
@@ -85,7 +85,7 @@ describe('Dashboard site observability panel', () => {
});
it('renders site availability strips and summary metrics', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -69,7 +69,7 @@ describe('Dashboard site speed buttons', () => {
});
it('updates site speed status without imperative document lookups', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -99,7 +99,7 @@ describe('Dashboard site speed buttons', () => {
});
it('updates bulk site speed status without imperative document lookups', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
+1 -1
View File
@@ -40,7 +40,7 @@ describe('Dashboard hook order', () => {
});
it('keeps hook order stable when switching from loading to loaded render', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
let renderedTree = '';
const dashboardData = {
totalBalance: 0,
@@ -180,7 +180,7 @@ describe('DownstreamKeys mobile layout', () => {
});
it('renders mobile cards and supports select-all-visible batch actions', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
+8 -6
View File
@@ -72,7 +72,7 @@ describe('buildSiteSaveAction', () => {
it('throws when edit mode has no site id', () => {
expect(() =>
buildSiteSaveAction(
{ mode: 'edit' },
{ mode: 'edit' } as unknown as Parameters<typeof buildSiteSaveAction>[0],
{
name: 'site-c',
url: 'https://c.example.com',
@@ -88,10 +88,7 @@ describe('buildSiteSaveAction', () => {
});
it('does not expose deprecated apiKey in site editor state', () => {
expect(emptySiteForm()).not.toHaveProperty('apiKey');
expect(emptySiteForm().customHeaders).toEqual([emptySiteCustomHeader()]);
expect(emptySiteForm().proxyUrl).toBe('');
expect(siteFormFromSite({
const legacySite = {
name: 'site-d',
url: 'https://d.example.com',
externalCheckinUrl: null,
@@ -100,7 +97,12 @@ describe('buildSiteSaveAction', () => {
customHeaders: '{"x-site-token":"alpha"}',
globalWeight: 1,
apiKey: 'sk-legacy-site-key',
})).not.toHaveProperty('apiKey');
} as unknown as Parameters<typeof siteFormFromSite>[0];
expect(emptySiteForm()).not.toHaveProperty('apiKey');
expect(emptySiteForm().customHeaders).toEqual([emptySiteCustomHeader()]);
expect(emptySiteForm().proxyUrl).toBe('');
expect(siteFormFromSite(legacySite)).not.toHaveProperty('apiKey');
expect(siteFormFromSite({
proxyUrl: 'http://127.0.0.1:8080',
}).proxyUrl).toBe('http://127.0.0.1:8080');
+2 -2
View File
@@ -105,7 +105,7 @@ describe('Models mobile layout', () => {
});
it('keeps a mobile filter entry and hides the table-view toggle on small screens', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -130,7 +130,7 @@ describe('Models mobile layout', () => {
});
it('renders stacked account detail cards instead of tables when a mobile card expands', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
+1 -1
View File
@@ -68,7 +68,7 @@ describe('ProgramLogs mobile layout', () => {
});
it('renders mobile cards instead of only the desktop table on small screens', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -116,7 +116,7 @@ describe('Settings factory reset', () => {
});
it('shows a 3 second danger confirmation and clears local state after reset', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -77,7 +77,7 @@ describe('Settings log cleanup schedule', () => {
});
it('saves schedule mode and interval fields together with other schedule settings', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -119,7 +119,7 @@ describe('Settings log cleanup schedule', () => {
it('triggers a one-off checkin from the schedule card', async () => {
apiMock.triggerCheckinAll.mockResolvedValue({ success: true });
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -150,7 +150,7 @@ describe('Settings log cleanup schedule', () => {
});
it('renders schedule mode controls with modern selects and ghost action styling', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
+2 -2
View File
@@ -87,7 +87,7 @@ describe('Settings system proxy', () => {
});
it('saves system proxy url from settings', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -127,7 +127,7 @@ describe('Settings system proxy', () => {
});
it('tests system proxy from settings', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
+1 -1
View File
@@ -85,7 +85,7 @@ describe('SiteAnnouncements page', () => {
});
it('loads announcements, highlights the focused row, and clears local rows', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
+1 -1
View File
@@ -42,7 +42,7 @@ async function createSiteAndCollectLocation(createdSite: { id: number; platform?
apiMock.getSites.mockResolvedValue([]);
apiMock.addSite.mockResolvedValue(createdSite);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -70,8 +70,8 @@ describe('Sites disabled models save', () => {
vi.clearAllMocks();
});
async function renderSitesEditor() {
let root: ReturnType<typeof create> | null = null;
async function renderSitesEditor(): Promise<WebTestRenderer> {
let root!: WebTestRenderer;
await act(async () => {
root = create(
<MemoryRouter initialEntries={['/sites']}>
@@ -83,7 +83,7 @@ describe('Sites disabled models save', () => {
});
await flushMicrotasks();
const editButton = root.root.find((node) => (
const editButton = root.root.find((node: ReactTestInstance) => (
node.type === 'button'
&& typeof node.props.onClick === 'function'
&& collectText(node).trim() === '编辑'
@@ -100,7 +100,7 @@ describe('Sites disabled models save', () => {
it('reports route rebuild failure after saving disabled models from sites editor', async () => {
apiMock.rebuildRoutes.mockRejectedValue(new Error('rebuild failed'));
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
root = await renderSitesEditor();
@@ -129,11 +129,11 @@ describe('Sites disabled models save', () => {
apiMock.getSiteAvailableModels.mockRejectedValue(new Error('available models failed'));
apiMock.rebuildRoutes.mockResolvedValue({ success: true });
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
root = await renderSitesEditor();
const saveButton = root.root.find((node) => (
const saveButton = root.root.find((node: ReactTestInstance) => (
node.type === 'button'
&& typeof node.props.onClick === 'function'
&& collectText(node).includes('保存禁用列表')
@@ -146,18 +146,18 @@ describe('Sites disabled models save', () => {
expect(apiMock.updateSiteDisabledModels).toHaveBeenCalledWith(1, ['gpt-4o']);
} finally {
root?.unmount();
root.unmount();
}
});
it('allows adding a disabled model manually when no available models are discovered', async () => {
apiMock.rebuildRoutes.mockResolvedValue({ success: true });
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
root = await renderSitesEditor();
const manualInput = root.root.find((node) => (
const manualInput = root.root.find((node: ReactTestInstance) => (
node.type === 'input'
&& node.props.placeholder === '输入模型名称,如 gpt-4o'
));
@@ -166,7 +166,7 @@ describe('Sites disabled models save', () => {
manualInput.props.onChange({ target: { value: 'gpt-4o' } });
});
const addButton = root.root.find((node) => (
const addButton = root.root.find((node: ReactTestInstance) => (
node.type === 'button'
&& typeof node.props.onClick === 'function'
&& collectText(node).trim() === '添加模型'
@@ -177,7 +177,7 @@ describe('Sites disabled models save', () => {
});
await flushMicrotasks();
const saveButton = root.root.find((node) => (
const saveButton = root.root.find((node: ReactTestInstance) => (
node.type === 'button'
&& typeof node.props.onClick === 'function'
&& collectText(node).includes('保存禁用列表')
@@ -190,7 +190,7 @@ describe('Sites disabled models save', () => {
expect(apiMock.updateSiteDisabledModels).toHaveBeenCalledWith(1, ['gpt-4o']);
} finally {
root?.unmount();
root.unmount();
}
});
});
+2 -2
View File
@@ -58,7 +58,7 @@ describe('Sites edit behavior', () => {
value: scrollToMock,
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -91,7 +91,7 @@ describe('Sites edit behavior', () => {
it('restores header add button label after closing add modal', async () => {
apiMock.getSites.mockResolvedValue([]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
+1 -1
View File
@@ -59,7 +59,7 @@ describe('Sites mobile actions', () => {
});
it('supports select-all-visible and preserves the primary site url in mobile cards', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -55,7 +55,7 @@ describe('Sites system proxy bulk actions', () => {
});
it('sends selected site ids to enable system proxy', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -92,7 +92,7 @@ describe('Sites system proxy bulk actions', () => {
});
it('selects a site when clicking the row instead of only the checkbox', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
+1
View File
@@ -1,6 +1,7 @@
import type { ReactNode } from 'react';
import type { BrandInfo } from '../../components/BrandIcon.js';
import type { RouteDecision, RouteDecisionCandidate, RouteMode } from '../../../shared/tokenRouteContract.js';
export type { RouteDecision, RouteDecisionCandidate, RouteMode } from '../../../shared/tokenRouteContract.js';
export type RouteSortBy = 'modelPattern' | 'channelCount';
export type RouteSortDir = 'asc' | 'desc';
@@ -25,8 +25,11 @@ export function useRouteChannels() {
}
}, []);
const invalidateChannels = useCallback((routeId: number) => {
const invalidateChannels = useCallback((routeId?: number) => {
setChannelsByRouteId((prev) => {
if (routeId === undefined) {
return {};
}
const next = { ...prev };
delete next[routeId];
return next;
@@ -122,7 +122,7 @@ describe('TokenRoutes grouped source models', () => {
},
]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -181,7 +181,7 @@ describe('TokenRoutes grouped source models', () => {
]);
apiMock.getRouteChannels.mockResolvedValue(channels);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -241,7 +241,7 @@ describe('TokenRoutes grouped source models', () => {
]);
apiMock.getRouteChannels.mockResolvedValue(channels);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -296,7 +296,7 @@ describe('TokenRoutes grouped source models', () => {
});
apiMock.getRouteChannels.mockResolvedValue([]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -336,7 +336,7 @@ describe('TokenRoutes grouped source models', () => {
},
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -382,7 +382,7 @@ describe('TokenRoutes grouped source models', () => {
},
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -449,7 +449,7 @@ describe('TokenRoutes grouped source models', () => {
},
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -497,7 +497,7 @@ describe('TokenRoutes grouped source models', () => {
});
apiMock.getRouteChannels.mockResolvedValue([]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -543,7 +543,7 @@ describe('TokenRoutes grouped source models', () => {
},
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -594,7 +594,7 @@ describe('TokenRoutes grouped source models', () => {
},
]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -640,7 +640,7 @@ describe('TokenRoutes grouped source models', () => {
endpointTypesByModel: {},
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -683,7 +683,7 @@ describe('TokenRoutes grouped source models', () => {
endpointTypesByModel: {},
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -735,7 +735,7 @@ describe('TokenRoutes grouped source models', () => {
},
]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -774,7 +774,7 @@ describe('TokenRoutes grouped source models', () => {
},
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -824,7 +824,7 @@ describe('TokenRoutes grouped source models', () => {
},
]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -855,7 +855,7 @@ describe('TokenRoutes grouped source models', () => {
},
]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -928,7 +928,7 @@ describe('TokenRoutes grouped source models', () => {
},
});
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -1035,7 +1035,7 @@ describe('TokenRoutes grouped source models', () => {
},
]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -1089,7 +1089,7 @@ describe('TokenRoutes grouped source models', () => {
},
]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -1159,7 +1159,7 @@ describe('TokenRoutes grouped source models', () => {
},
]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -1231,7 +1231,7 @@ describe('TokenRoutes grouped source models', () => {
]);
apiMock.getRouteChannels.mockResolvedValue([]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -1317,7 +1317,7 @@ describe('TokenRoutes grouped source models', () => {
]);
apiMock.getRouteChannels.mockResolvedValue([]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -1412,7 +1412,7 @@ describe('TokenRoutes grouped source models', () => {
},
]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -137,7 +137,7 @@ describe('TokenRoutes mobile layout', () => {
});
it('shows direct mobile actions and reveals the management panel after expansion', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -178,7 +178,7 @@ describe('TokenRoutes mobile layout', () => {
});
it('lets mobile users toggle route enabled state from the summary card', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
+1 -1
View File
@@ -125,7 +125,7 @@ describe('TokenRoutes mobile actions', () => {
});
it('shows mobile detail expansion and direct management actions', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
@@ -81,7 +81,7 @@ describe('TokenRoutes refresh decision action', () => {
});
it('passes refreshPricingCatalog and persistSnapshots when user clicks refresh selection probability', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -79,7 +79,7 @@ describe('TokenRoutes routing strategy updates', () => {
});
it('keeps the optimistic routing strategy when refresh fails after a successful save', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -129,7 +129,7 @@ describe('TokenRoutes routing strategy updates', () => {
});
it('supports switching to stable_first and keeps the optimistic label when refresh fails', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -113,7 +113,7 @@ describe('TokenRoutes cached snapshot load', () => {
});
it('shows cached probabilities immediately from getRoutes snapshot data', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
+2 -2
View File
@@ -71,7 +71,7 @@ describe('Tokens batch actions', () => {
});
it('deletes selected tokens through the batch toolbar', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -117,7 +117,7 @@ describe('Tokens batch actions', () => {
});
it('selects a token when clicking the row instead of only the checkbox', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
@@ -143,7 +143,7 @@ describe('Tokens edit modal and row selection', () => {
});
it('opens the centered edit modal when editing a token', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = buildRoot();
@@ -191,7 +191,7 @@ describe('Tokens edit modal and row selection', () => {
});
it('selects a token when clicking the row body, but not when clicking an action button', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = buildRoot();
@@ -232,7 +232,7 @@ describe('Tokens edit modal and row selection', () => {
it('does not repeatedly refetch groups when edit-group loading fails once', async () => {
apiMock.getAccountTokenGroups.mockRejectedValueOnce(new Error('账号会话可能已过期,请重新登录后再拉取分组'));
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = buildRoot();
@@ -274,7 +274,7 @@ describe('Tokens edit modal and row selection', () => {
]);
apiMock.getAccountTokenValue.mockRejectedValueOnce(new Error('当前仅保存了脱敏令牌,无法展开/复制。请在站点重新生成并同步,或手动更新为完整令牌。'));
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = buildTokensRoot();
@@ -356,7 +356,7 @@ describe('Tokens edit modal and row selection', () => {
});
apiMock.getAccountTokenValue.mockRejectedValueOnce(new Error('当前仅保存了脱敏令牌,无法展开/复制。请在站点重新生成并同步,或手动更新为完整令牌。'));
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = buildTokensRoot();
+1 -1
View File
@@ -71,7 +71,7 @@ describe('Tokens focus navigation', () => {
},
]);
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
+1 -1
View File
@@ -77,7 +77,7 @@ describe('Tokens mobile actions', () => {
});
it('supports select-all-visible and expandable mobile token details', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
try {
await act(async () => {
root = create(
+1 -1
View File
@@ -23,7 +23,7 @@ function LocationProbe() {
describe('Tokens legacy route redirect', () => {
it('redirects /tokens to the merged accounts tokens segment', async () => {
let root: ReturnType<typeof create> | null = null;
let root!: WebTestRenderer;
await act(async () => {
root = create(
<ToastProvider>
+1
View File
@@ -0,0 +1 @@
type WebTestRenderer = import('react-test-renderer').ReactTestRenderer;