import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { vi } from 'vitest'; import { http, HttpResponse } from 'msw'; import { withTestQueryProvider } from '@/react/test-utils/withTestQuery'; import { withTestRouter } from '@/react/test-utils/withRouter'; import { withUserProvider } from '@/react/test-utils/withUserProvider'; import { server } from '@/setup-tests/server'; import { createMockUser } from '@/react-tools/test-mocks'; import { CreateImageSection } from './CreateImageSection'; vi.mock('@uirouter/react', async (importOriginal: () => Promise) => ({ ...(await importOriginal()), useCurrentStateAndParams: vi.fn(() => ({ params: { endpointId: 1 }, })), })); vi.mock('@/portainer/services/notifications', () => ({ notifySuccess: vi.fn(), })); describe('CreateImageSection', () => { beforeEach(() => { // Setup MSW handlers for registries API server.use( http.get('/api/endpoints/:endpointId/registries', () => HttpResponse.json([ { Id: 1, Name: 'DockerHub', Type: 1, URL: 'docker.io', Username: 'testuser', }, { Id: 2, Name: 'Private Registry', Type: 3, URL: 'registry.example.com', Username: '', }, ]) ), // Mock images endpoint for autocomplete functionality http.get('/api/endpoints/:endpointId/docker/images/json', () => HttpResponse.json([ { Id: 'sha256:abc123', RepoTags: ['docker.io/testuser/my-app:latest', 'nginx:latest'], }, { Id: 'sha256:def456', RepoTags: ['registry.example.com/another-app:v1.0'], }, ]) ) ); }); afterEach(() => { vi.clearAllMocks(); }); describe('Authorization', () => { it('should render widget when user has DockerImageCreate authorization', async () => { renderComponent({ userAuthorizations: { DockerImageCreate: true, }, }); await waitFor(() => { expect( screen.getByRole('heading', { name: /create image/i }) ).toBeVisible(); }); }); it('should not render when user lacks authorization', () => { renderComponent({ userAuthorizations: { DockerImageCreate: false, }, }); expect( screen.queryByRole('heading', { name: /create image/i }) ).not.toBeInTheDocument(); }); }); describe('Basic Rendering', () => { it('should render widget with title', async () => { renderComponent(); await waitFor(() => { expect( screen.getByRole('heading', { name: /create image/i }) ).toBeVisible(); }); }); it('should render description text', async () => { renderComponent(); await waitFor(() => { expect( screen.getByText(/you can create an image from this container/i) ).toBeVisible(); }); }); }); describe('Image Creation', () => { it('should successfully create image and call onSuccess', async () => { const onSuccess = vi.fn(); let capturedRequest: { container?: string; repo?: string; tag?: string; } = {}; // Setup MSW handler for commit server.use( http.post( '/api/endpoints/:endpointId/docker/commit', async ({ request }) => { const url = new URL(request.url); capturedRequest = { container: url.searchParams.get('container') || undefined, repo: url.searchParams.get('repo') || undefined, tag: url.searchParams.get('tag') || undefined, }; return HttpResponse.json({ Id: 'sha256:abc123' }); } ) ); renderComponent({ onSuccess }); // Wait for form to load await waitFor(() => { expect(screen.getByRole('combobox', { name: 'Image' })).toBeVisible(); }); // Fill in image name const imageInput = screen.getByRole('combobox', { name: 'Image' }); await userEvent.type(imageInput, 'my-app:v1.0'); // Submit form const createButton = screen.getByRole('button', { name: /^create$/i }); await waitFor(() => { expect(createButton).not.toBeDisabled(); }); await userEvent.click(createButton); // Verify API call await waitFor(() => { expect(capturedRequest.container).toBe('container123'); expect(capturedRequest.repo).toBe('my-app'); expect(capturedRequest.tag).toBe('v1.0'); }); // Verify onSuccess callback await waitFor(() => { expect(onSuccess).toHaveBeenCalled(); }); }); it('should handle API error', async () => { const onMutationError = vi.fn(); const consoleErrorSpy = vi .spyOn(console, 'error') .mockImplementation(() => {}); server.use( http.post('/api/endpoints/:endpointId/docker/commit', () => HttpResponse.json({ message: 'Container not found' }, { status: 404 }) ) ); renderComponent({ onMutationError }); await waitFor(() => { expect(screen.getByRole('combobox', { name: 'Image' })).toBeVisible(); }); const imageInput = screen.getByRole('combobox', { name: 'Image' }); await userEvent.type(imageInput, 'my-app'); const createButton = screen.getByRole('button', { name: /^create$/i }); await waitFor(() => { expect(createButton).not.toBeDisabled(); }); await userEvent.click(createButton); // Wait for the mutation error to be handled await waitFor( () => { expect(onMutationError).toHaveBeenCalled(); }, { timeout: 3000 } ); consoleErrorSpy.mockRestore(); }); it('should show loading state during creation', async () => { server.use( http.post('/api/endpoints/:endpointId/docker/commit', async () => { await new Promise((resolve) => { setTimeout(resolve, 100); }); return HttpResponse.json({ Id: 'sha256:abc123' }); }) ); renderComponent(); await waitFor(() => { expect(screen.getByRole('combobox', { name: 'Image' })).toBeVisible(); }); const imageInput = screen.getByRole('combobox', { name: 'Image' }); await userEvent.type(imageInput, 'my-app'); const createButton = screen.getByRole('button', { name: /^create$/i }); await waitFor(() => { expect(createButton).not.toBeDisabled(); }); await userEvent.click(createButton); // Check loading state await waitFor(() => { expect( screen.getByRole('button', { name: /creating image/i }) ).toBeDisabled(); }); }); }); }); function renderComponent({ userAuthorizations, onMutationError, ...componentProps }: Partial> & { userAuthorizations?: Record; onMutationError?: (error: unknown) => void; } = {}) { const defaultProps: React.ComponentProps = { environmentId: 1, containerId: 'container123', ...componentProps, }; const defaultAuthorizations = { DockerImageCreate: true, }; const mockUser = createMockUser({ EndpointAuthorizations: { 1: userAuthorizations || defaultAuthorizations, }, Id: 1, Role: 1, }); const Wrapper = withTestQueryProvider( withUserProvider(withTestRouter(CreateImageSection), mockUser), onMutationError ? { onMutationError } : undefined ); return render(); }