/**
* @license
* Copyright 2025 AionUi (aionui.com)
* SPDX-License-Identifier: Apache-2.0
*/
import React from 'react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { SettingsTabNavigateProvider } from '@/renderer/components/settings/SettingsModal/settingsViewContext';
const hooks = vi.hoisted(() => ({
modelListWithImage: [] as unknown[],
mcpServers: [] as unknown[],
getClientBusinessSetting: vi.fn(() => Promise.resolve(undefined)),
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({ t: (key: string, options?: { defaultValue?: string }) => options?.defaultValue ?? key }),
}));
vi.mock('@/renderer/components/base/AionScrollArea', () => ({
default: ({ children }: { children: React.ReactNode }) =>
{children}
,
}));
vi.mock('@/renderer/components/base/AionSelect', () => {
const Select = ({ children }: { children?: React.ReactNode }) => {children}
;
return { default: Object.assign(Select, { OptGroup: Select, Option: Select }) };
});
vi.mock('@/renderer/components/base/TalkToButlerButton', () => ({
default: () => TalkToButlerButton
,
}));
vi.mock('@/renderer/pages/settings/components/AddMcpServerModal', () => ({
default: () => null,
}));
vi.mock('@/renderer/pages/settings/ToolsSettings/McpServerItem', () => ({
default: () => null,
}));
vi.mock('@/renderer/hooks/agent/useConfigModelListWithImage', () => ({
default: () => ({ modelListWithImage: hooks.modelListWithImage }),
}));
vi.mock('@/renderer/hooks/mcp', () => ({
useMcpServers: () => ({
mcpServers: hooks.mcpServers,
extensionMcpServers: [],
saveMcpServers: vi.fn(() => Promise.resolve()),
setMcpServers: vi.fn(),
isMcpServersLoading: false,
}),
useMcpConnection: () => ({ testingServers: {}, handleTestMcpConnection: vi.fn(), handleTestMcpConnections: vi.fn() }),
useMcpModal: () => ({
showMcpModal: false,
editingMcpServer: undefined,
deleteConfirmVisible: false,
serverToDelete: undefined,
mcpCollapseKey: [],
showAddMcpModal: vi.fn(),
showEditMcpModal: vi.fn(),
hideMcpModal: vi.fn(),
showDeleteConfirm: vi.fn(),
hideDeleteConfirm: vi.fn(),
toggleServerCollapse: vi.fn(),
}),
useMcpServerCRUD: () => ({
handleAddMcpServer: vi.fn(),
handleBatchImportMcpServers: vi.fn(),
handleEditMcpServer: vi.fn(),
handleDeleteMcpServer: vi.fn(),
}),
useMcpOAuth: () => ({
oauthStatus: {},
loggingIn: {},
checkOAuthStatus: vi.fn(),
markLoginRequired: vi.fn(),
clearLoginRequired: vi.fn(),
login: vi.fn(),
}),
useMountedMessage: (m: unknown) => m,
}));
vi.mock('@/renderer/services/clientBusinessSettings', () => ({
getClientBusinessSetting: hooks.getClientBusinessSetting,
setClientBusinessSetting: vi.fn(() => Promise.resolve()),
removeClientBusinessSetting: vi.fn(() => Promise.resolve()),
}));
vi.mock('@/common/adapter/ipcBridge', () => ({
mcpService: {},
}));
import ToolsModalContent from '@/renderer/components/settings/SettingsModal/contents/ToolsModalContent';
describe('ToolsModalContent image model guide', () => {
beforeEach(() => {
hooks.modelListWithImage = [];
hooks.mcpServers = [];
hooks.getClientBusinessSetting.mockClear();
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation((query: string) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
});
});
afterEach(() => {
cleanup();
});
it('renders a clickable "go to configure" link that navigates to the model tab', async () => {
const navigateToTab = vi.fn();
render(
);
const link = await screen.findByText('settings.goToModelSettings');
// Rendered as an inline anchor (text link), not a button.
expect(link.tagName).toBe('A');
fireEvent.click(link);
await waitFor(() => expect(navigateToTab).toHaveBeenCalledWith('model'));
});
it('renders the guide text as plain text (no link) when no tab navigator is provided', async () => {
const { container } = render();
// The empty-state hint still shows the go-to-configure wording, but not as a clickable link.
await waitFor(() => expect(container.textContent).toContain('settings.goToModelSettings'));
const links = Array.from(container.querySelectorAll('a')).filter(
(a) => a.textContent === 'settings.goToModelSettings'
);
expect(links).toHaveLength(0);
});
});