Skip to content

Commit 1c5e0fb

Browse files
add unit tests (opendatahub-io#5337)
1 parent 79e19c9 commit 1c5e0fb

File tree

10 files changed

+1655
-0
lines changed

10 files changed

+1655
-0
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/* eslint-disable camelcase */
2+
import * as React from 'react';
3+
import { render, screen } from '@testing-library/react';
4+
import userEvent from '@testing-library/user-event';
5+
import DeleteFileModal from '~/app/Chatbot/components/DeleteFileModal';
6+
import { FileModel } from '~/app/types';
7+
8+
describe('DeleteFileModal', () => {
9+
const mockFile: FileModel = {
10+
id: 'file-123',
11+
filename: 'test-document.pdf',
12+
bytes: 1024,
13+
created_at: 1609459200,
14+
purpose: 'assistants',
15+
object: 'file',
16+
status: 'completed',
17+
expires_at: 1612137600,
18+
status_details: '',
19+
};
20+
21+
const defaultProps = {
22+
isOpen: true,
23+
file: mockFile,
24+
onClose: jest.fn(),
25+
onConfirm: jest.fn(),
26+
isDeleting: false,
27+
};
28+
29+
beforeEach(() => {
30+
jest.clearAllMocks();
31+
});
32+
33+
it('renders nothing when file is null', () => {
34+
const { container } = render(<DeleteFileModal {...defaultProps} file={null} />);
35+
expect(container.firstChild).toBeNull();
36+
});
37+
38+
it('renders modal with file information', () => {
39+
render(<DeleteFileModal {...defaultProps} />);
40+
41+
expect(screen.getByText('Delete file?')).toBeInTheDocument();
42+
expect(screen.getByText(/test-document.pdf/i)).toBeInTheDocument();
43+
expect(screen.getByText(/file will be deleted/i)).toBeInTheDocument();
44+
});
45+
46+
it('calls onConfirm when delete button is clicked', async () => {
47+
const user = userEvent.setup();
48+
const mockOnConfirm = jest.fn();
49+
50+
render(<DeleteFileModal {...defaultProps} onConfirm={mockOnConfirm} />);
51+
52+
const deleteButton = screen.getByRole('button', { name: /delete/i });
53+
await user.click(deleteButton);
54+
55+
expect(mockOnConfirm).toHaveBeenCalledTimes(1);
56+
});
57+
58+
it('calls onClose when cancel button is clicked', async () => {
59+
const user = userEvent.setup();
60+
const mockOnClose = jest.fn();
61+
62+
render(<DeleteFileModal {...defaultProps} onClose={mockOnClose} />);
63+
64+
const cancelButton = screen.getByRole('button', { name: /cancel/i });
65+
await user.click(cancelButton);
66+
67+
expect(mockOnClose).toHaveBeenCalledTimes(1);
68+
});
69+
70+
it('disables buttons when deleting', () => {
71+
render(<DeleteFileModal {...defaultProps} isDeleting />);
72+
73+
const deleteButton = screen.getByRole('button', { name: /delete/i });
74+
const cancelButton = screen.getByRole('button', { name: /cancel/i });
75+
76+
expect(deleteButton).toBeDisabled();
77+
expect(cancelButton).toBeDisabled();
78+
});
79+
80+
it('does not render when isOpen is false', () => {
81+
render(<DeleteFileModal {...defaultProps} isOpen={false} />);
82+
83+
expect(screen.queryByText('Delete file?')).not.toBeInTheDocument();
84+
});
85+
});
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
import * as React from 'react';
2+
import { render, screen, waitFor } from '@testing-library/react';
3+
import userEvent from '@testing-library/user-event';
4+
import { fireFormTrackingEvent } from '@odh-dashboard/internal/concepts/analyticsTracking/segmentIOUtils';
5+
import { TrackingOutcome } from '@odh-dashboard/internal/concepts/analyticsTracking/trackingProperties';
6+
import DeletePlaygroundModal from '~/app/Chatbot/components/DeletePlaygroundModal';
7+
import { GenAiContext } from '~/app/context/GenAiContext';
8+
import { ChatbotContext } from '~/app/context/ChatbotContext';
9+
import { deleteLSD } from '~/app/services/llamaStackService';
10+
11+
jest.mock('~/app/services/llamaStackService');
12+
jest.mock('@odh-dashboard/internal/concepts/analyticsTracking/segmentIOUtils');
13+
14+
jest.mock('~/app/shared/DeleteModal', () => ({
15+
__esModule: true,
16+
default: ({
17+
title,
18+
onClose,
19+
onDelete,
20+
deleting,
21+
error,
22+
submitButtonLabel,
23+
deleteName,
24+
children,
25+
}: {
26+
title: string;
27+
onClose: () => void;
28+
onDelete: () => void;
29+
deleting: boolean;
30+
error?: Error;
31+
submitButtonLabel: string;
32+
deleteName: string;
33+
children: React.ReactNode;
34+
}) => (
35+
<div data-testid="delete-modal">
36+
<h1>{title}</h1>
37+
<p>{children}</p>
38+
<p>Deleting: {deleteName}</p>
39+
{error && <div data-testid="error">{error.message}</div>}
40+
<button onClick={onDelete} disabled={deleting}>
41+
{submitButtonLabel}
42+
</button>
43+
<button onClick={onClose}>Cancel</button>
44+
</div>
45+
),
46+
}));
47+
48+
const mockDeleteLSD = jest.mocked(deleteLSD);
49+
const mockFireFormTrackingEvent = jest.mocked(fireFormTrackingEvent);
50+
51+
const mockNamespace = { name: 'test-namespace', displayName: 'Test Namespace' };
52+
const mockGenAiContextValue = {
53+
namespace: mockNamespace,
54+
isLoading: false,
55+
error: undefined,
56+
};
57+
58+
const mockLsdStatus = {
59+
name: 'test-lsd',
60+
phase: 'Ready' as const,
61+
version: '1.0.0',
62+
distributionConfig: {
63+
activeDistribution: 'test-dist',
64+
providers: [],
65+
availableDistributions: {},
66+
},
67+
};
68+
69+
const mockChatbotContextValue = {
70+
lsdStatus: mockLsdStatus,
71+
refresh: jest.fn(),
72+
models: [],
73+
modelsLoaded: true,
74+
lsdStatusLoaded: true,
75+
aiModels: [],
76+
aiModelsLoaded: true,
77+
aiModelsError: undefined,
78+
maasModels: [],
79+
maasModelsLoaded: true,
80+
maasModelsError: undefined,
81+
modelsError: undefined,
82+
lsdStatusError: undefined,
83+
selectedModel: '',
84+
setSelectedModel: jest.fn(),
85+
lastInput: '',
86+
setLastInput: jest.fn(),
87+
};
88+
89+
const TestWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (
90+
<GenAiContext.Provider value={mockGenAiContextValue as React.ContextType<typeof GenAiContext>}>
91+
<ChatbotContext.Provider
92+
value={mockChatbotContextValue as React.ContextType<typeof ChatbotContext>}
93+
>
94+
{children}
95+
</ChatbotContext.Provider>
96+
</GenAiContext.Provider>
97+
);
98+
99+
describe('DeletePlaygroundModal', () => {
100+
const defaultProps = {
101+
onCancel: jest.fn(),
102+
};
103+
104+
beforeEach(() => {
105+
jest.clearAllMocks();
106+
});
107+
108+
it('renders delete modal with correct content', () => {
109+
render(
110+
<TestWrapper>
111+
<DeletePlaygroundModal {...defaultProps} />
112+
</TestWrapper>,
113+
);
114+
115+
expect(screen.getByText('Delete playground?')).toBeInTheDocument();
116+
expect(
117+
screen.getByText(/This action cannot be undone. This will delete the playground/i),
118+
).toBeInTheDocument();
119+
expect(screen.getByText(/Deleting: Test Namespace/i)).toBeInTheDocument();
120+
});
121+
122+
it('calls onCancel and fires cancel tracking event', async () => {
123+
const user = userEvent.setup();
124+
const mockOnCancel = jest.fn();
125+
126+
render(
127+
<TestWrapper>
128+
<DeletePlaygroundModal onCancel={mockOnCancel} />
129+
</TestWrapper>,
130+
);
131+
132+
const cancelButton = screen.getByRole('button', { name: /cancel/i });
133+
await user.click(cancelButton);
134+
135+
expect(mockOnCancel).toHaveBeenCalledTimes(1);
136+
expect(mockFireFormTrackingEvent).toHaveBeenCalledWith('Playground Delete', {
137+
outcome: TrackingOutcome.cancel,
138+
namespace: 'test-namespace',
139+
});
140+
});
141+
142+
it('successfully deletes playground', async () => {
143+
const user = userEvent.setup();
144+
const mockOnCancel = jest.fn();
145+
const mockRefresh = jest.fn();
146+
147+
mockDeleteLSD.mockResolvedValue('success');
148+
149+
render(
150+
<GenAiContext.Provider
151+
value={mockGenAiContextValue as React.ContextType<typeof GenAiContext>}
152+
>
153+
<ChatbotContext.Provider
154+
value={
155+
{ ...mockChatbotContextValue, refresh: mockRefresh } as React.ContextType<
156+
typeof ChatbotContext
157+
>
158+
}
159+
>
160+
<DeletePlaygroundModal onCancel={mockOnCancel} />
161+
</ChatbotContext.Provider>
162+
</GenAiContext.Provider>,
163+
);
164+
165+
const deleteButton = screen.getByRole('button', { name: /delete/i });
166+
await user.click(deleteButton);
167+
168+
await waitFor(() => {
169+
expect(mockDeleteLSD).toHaveBeenCalledWith('test-namespace', 'test-lsd');
170+
});
171+
172+
await waitFor(() => {
173+
expect(mockOnCancel).toHaveBeenCalled();
174+
expect(mockRefresh).toHaveBeenCalled();
175+
expect(mockFireFormTrackingEvent).toHaveBeenCalledWith('Playground Delete', {
176+
outcome: TrackingOutcome.submit,
177+
success: true,
178+
namespace: 'test-namespace',
179+
});
180+
});
181+
});
182+
183+
it('handles deletion error', async () => {
184+
const user = userEvent.setup();
185+
const mockError = new Error('Failed to delete');
186+
187+
mockDeleteLSD.mockRejectedValue(mockError);
188+
189+
render(
190+
<TestWrapper>
191+
<DeletePlaygroundModal {...defaultProps} />
192+
</TestWrapper>,
193+
);
194+
195+
const deleteButton = screen.getByRole('button', { name: /delete/i });
196+
await user.click(deleteButton);
197+
198+
await waitFor(() => {
199+
expect(screen.getByTestId('error')).toHaveTextContent('Failed to delete');
200+
});
201+
202+
await waitFor(() => {
203+
expect(mockFireFormTrackingEvent).toHaveBeenCalledWith('Playground Delete', {
204+
outcome: TrackingOutcome.submit,
205+
success: false,
206+
namespace: 'test-namespace',
207+
error: 'Failed to delete',
208+
});
209+
});
210+
});
211+
});

0 commit comments

Comments
 (0)