/** * @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); }); });