Files
spoon/apps/next/tests/component/ai-provider-profiles-error.test.tsx
T

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();
});
});