import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'; import { getFunctionName } from 'convex/server'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { api } from '@spoon/backend/convex/_generated/api.js'; import { toast } from 'sonner'; import { AiProviderProfilesPanel } from '../../src/components/integrations/ai-provider-profiles-panel'; // Radix Select (rendered by the panel form) relies on ResizeObserver, which // jsdom does not implement. class ResizeObserverStub { observe = vi.fn(); unobserve = vi.fn(); disconnect = vi.fn(); } globalThis.ResizeObserver ??= ResizeObserverStub as never; const { mockUseAction, mockUseMutation, mockUseQuery } = vi.hoisted(() => ({ mockUseAction: vi.fn(), mockUseMutation: vi.fn(), mockUseQuery: vi.fn(), })); vi.mock('convex/react', () => ({ useAction: mockUseAction, useMutation: mockUseMutation, useQuery: mockUseQuery, })); vi.mock('sonner', () => ({ toast: { error: vi.fn(), success: vi.fn(), }, })); const profile = { _id: 'profile-1', name: 'OpenAI', provider: 'openai', authType: 'api_key', secretPreview: 'sk-***', defaultModel: 'gpt-4o', reasoningEffort: 'medium', isDefault: true, enabled: true, configured: true, baseUrl: undefined, modelOptions: ['gpt-4o'], }; const removeName = getFunctionName(api.aiProviderProfiles.remove); const setup = (removeSpy: ReturnType) => { mockUseAction.mockReturnValue(vi.fn()); mockUseQuery.mockReturnValue([profile]); mockUseMutation.mockImplementation((ref: Parameters[0]) => { if (getFunctionName(ref) === removeName) return removeSpy; return vi.fn(); }); }; afterEach(() => { cleanup(); vi.clearAllMocks(); }); describe('AiProviderProfilesPanel remove handler', () => { it('shows an error toast and no success toast when the remove mutation rejects', async () => { const removeSpy = vi.fn().mockRejectedValue(new Error('boom')); setup(removeSpy); render(); fireEvent.click(screen.getByRole('button', { name: /remove provider/i })); await waitFor(() => expect(toast.error).toHaveBeenCalled()); expect(removeSpy).toHaveBeenCalledWith({ profileId: 'profile-1' }); expect(toast.error).toHaveBeenCalledWith('boom'); expect(toast.success).not.toHaveBeenCalled(); }); it('shows a success toast only after the remove mutation resolves', async () => { const removeSpy = vi.fn().mockResolvedValue(undefined); setup(removeSpy); render(); fireEvent.click(screen.getByRole('button', { name: /remove provider/i })); await waitFor(() => expect(toast.success).toHaveBeenCalledWith('AI provider removed.'), ); expect(toast.error).not.toHaveBeenCalled(); }); });