97 lines
2.8 KiB
TypeScript
97 lines
2.8 KiB
TypeScript
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<typeof vi.fn>) => {
|
|
mockUseAction.mockReturnValue(vi.fn());
|
|
mockUseQuery.mockReturnValue([profile]);
|
|
mockUseMutation.mockImplementation((ref: Parameters<typeof getFunctionName>[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(<AiProviderProfilesPanel />);
|
|
|
|
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(<AiProviderProfilesPanel />);
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: /remove provider/i }));
|
|
|
|
await waitFor(() =>
|
|
expect(toast.success).toHaveBeenCalledWith('AI provider removed.'),
|
|
);
|
|
expect(toast.error).not.toHaveBeenCalled();
|
|
});
|
|
});
|