forked from boos/BoosAPI
fix: tighten ci typecheck coverage
This commit is contained in:
@@ -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 () => {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]}>
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,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 () => {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -71,7 +71,7 @@ describe('Tokens focus navigation', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
let root: ReturnType<typeof create> | null = null;
|
||||
let root!: WebTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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>
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
type WebTestRenderer = import('react-test-renderer').ReactTestRenderer;
|
||||
Reference in New Issue
Block a user