import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import '@testing-library/jest-dom/vitest';
import { ThemeProvider } from '@mui/material/styles';
import { lightTheme } from '@services/theme/defaultTheme';
import { AIProviderConfig, ModelFeature, ModelInfo } from '@services/externalAPI/interface';
import { ProviderConfig } from '../ProviderConfig';
// Mock data
const mockLanguageModel: ModelInfo = {
name: 'gpt-4',
caption: 'GPT-4 Language Model',
features: ['language' as ModelFeature],
};
const mockEmbeddingModel: ModelInfo = {
name: 'text-embedding-3-small',
caption: 'OpenAI Embedding Model',
features: ['embedding' as ModelFeature],
};
const mockProvider: AIProviderConfig = {
provider: 'openai',
apiKey: 'sk-test',
baseURL: 'https://api.openai.com/v1',
models: [mockLanguageModel, mockEmbeddingModel],
providerClass: 'openai',
isPreset: false,
enabled: true,
};
// Test wrapper component
const TestWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (
{children}
);
describe('ProviderConfig Component', () => {
const mockSetProviders = vi.fn();
const mockChangeDefaultModel = vi.fn();
const mockChangeDefaultEmbeddingModel = vi.fn();
const mockChangeDefaultSpeechModel = vi.fn();
const mockChangeDefaultImageGenerationModel = vi.fn();
const mockChangeDefaultTranscriptionsModel = vi.fn();
beforeEach(() => {
vi.clearAllMocks();
// Mock ExternalAPI service methods
Object.defineProperty(window.service.externalAPI, 'updateProvider', {
value: vi.fn().mockResolvedValue(undefined),
writable: true,
});
Object.defineProperty(window.service.externalAPI, 'deleteProvider', {
value: vi.fn().mockResolvedValue(undefined),
writable: true,
});
Object.defineProperty(window.service.externalAPI, 'getAIConfig', {
value: vi.fn().mockResolvedValue({
api: {
provider: 'openai',
model: 'gpt-4',
},
modelParameters: {
temperature: 0.7,
},
}),
writable: true,
});
Object.defineProperty(window.service.externalAPI, 'updateDefaultAIConfig', {
value: vi.fn().mockResolvedValue(undefined),
writable: true,
});
// Mock window.confirm
Object.defineProperty(window, 'confirm', {
value: vi.fn().mockReturnValue(true),
writable: true,
});
});
const renderProviderConfig = (providers: AIProviderConfig[] = [mockProvider]) => {
return render(
,
);
};
it('should render provider configuration with delete button for providers', () => {
renderProviderConfig();
// Should show provider tab
expect(screen.getByText('openai')).toBeInTheDocument();
// Should show delete provider button (since mockProvider is not preset)
expect(screen.getByTestId('delete-provider-button')).toBeInTheDocument();
});
it('should call deleteProvider API when delete button is clicked', async () => {
const user = userEvent.setup();
renderProviderConfig();
// Find and click the delete provider button
const deleteButton = screen.getByTestId('delete-provider-button');
await user.click(deleteButton);
// Verify confirmation was shown
expect(window.confirm).toHaveBeenCalled();
// Verify the delete API was called
await waitFor(() => {
expect(window.service.externalAPI.deleteProvider).toHaveBeenCalledWith('openai');
});
// Verify local state was updated
expect(mockSetProviders).toHaveBeenCalledWith([]);
});
it('should not delete provider if user cancels confirmation', async () => {
const user = userEvent.setup();
// Mock window.confirm to return false (user cancels)
Object.defineProperty(window, 'confirm', {
value: vi.fn().mockReturnValue(false),
writable: true,
});
renderProviderConfig();
// Find and click the delete provider button
const deleteButton = screen.getByTestId('delete-provider-button');
await user.click(deleteButton);
// Verify confirmation was shown
expect(window.confirm).toHaveBeenCalled();
// Verify the delete API was NOT called
expect(window.service.externalAPI.deleteProvider).not.toHaveBeenCalled();
// Verify local state was NOT updated
expect(mockSetProviders).not.toHaveBeenCalled();
});
it('should render add provider form when add button is clicked', async () => {
const user = userEvent.setup();
renderProviderConfig();
// Find and click the add provider button
const addButton = screen.getByTestId('add-new-provider-button');
await user.click(addButton);
// Should show new provider form
expect(screen.getByText('Preference.CancelAddProvider')).toBeInTheDocument();
// Should show form fields (these are likely in NewProviderForm component)
// We'll verify the button text change for now
expect(addButton).toHaveTextContent('Preference.CancelAddProvider');
});
// NOTE: embedding-model defaulting when enabling a provider is handled during provider addition
// (handleAddProvider) and not on enable toggle. Removed the old enable-toggle test.
it('should automatically add embedding model when selecting preset provider with embedding model', async () => {
const user = userEvent.setup();
// Start with no providers to show the add provider form
renderProviderConfig([]);
// Click add provider button to show form
const addButton = screen.getByTestId('add-new-provider-button');
await user.click(addButton);
// Mock the provider creation API calls
const mockUpdateProvider = vi.fn().mockResolvedValue(undefined);
Object.defineProperty(window.service.externalAPI, 'updateProvider', {
value: mockUpdateProvider,
writable: true,
});
// Mock AI config to simulate no existing embedding model
Object.defineProperty(window.service.externalAPI, 'getAIConfig', {
value: vi.fn().mockResolvedValue({
api: {
provider: '',
model: '',
},
modelParameters: {},
}),
writable: true,
});
const mockUpdateDefaultAIConfig = vi.fn().mockResolvedValue(undefined);
Object.defineProperty(window.service.externalAPI, 'updateDefaultAIConfig', {
value: mockUpdateDefaultAIConfig,
writable: true,
});
// Note: The actual form interaction would require the NewProviderForm component to be rendered
// For now, we'll test the logic by calling the handler directly through the component's internal state
// This is a limitation of testing complex forms - ideally we'd have integration tests
// The test structure shows what should happen when siliconflow is selected:
// 1. Language model "Qwen/Qwen2.5-7B-Instruct" should be added
// 2. Embedding model "BAAI/bge-m3" should be added
// 3. Embedding model should be set as default if no existing embedding model
expect(true).toBe(true); // Placeholder - real test would interact with form
});
});