import { cleanup, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import ImageSection from './ImageSection'; import { renderWithIntl } from '@/utils/test/renderWithIntl'; // Mock getImageUrlOrFile vi.mock('@/utils/image', () => ({ getImageUrlOrFile: (url: string) => url, })); // Mock URL.createObjectURL global.URL.createObjectURL = vi.fn(() => 'https://mock-local-url'); describe('ImageSection', () => { const mockOnImageFileChange = vi.fn(); const mockOnImageUrlChange = vi.fn(); const mockOnDeleteImage = vi.fn(); beforeEach(() => { cleanup(); vi.clearAllMocks(); global.fetch = vi.fn(); }); it('renders default state without image', () => { renderWithIntl( , ); expect(screen.getByText('Upload from Device')).toBeInTheDocument(); expect(screen.queryByText('Search Unsplash')).not.toBeInTheDocument(); }); it('renders default state with Unsplash allowed', () => { renderWithIntl( , ); expect(screen.getByText('Upload from Device')).toBeInTheDocument(); expect(screen.getByText('Search Unsplash')).toBeInTheDocument(); }); it('renders image when currentImageUrl is provided', () => { renderWithIntl( , ); expect(screen.queryByText('Upload from Device')).not.toBeInTheDocument(); expect(screen.getByText('Change Image')).toBeInTheDocument(); expect(screen.getByText('Remove Current')).toBeInTheDocument(); expect(screen.getByAltText('Test Image')).toBeInTheDocument(); }); it('handles image file selection', async () => { const user = userEvent.setup(); renderWithIntl( , ); const fileList = [ new File(['dummy content'], 'test.png', { type: 'image/png' }), ]; const uploadInput = document.querySelector( 'input[type="file"]', ) as HTMLInputElement; await user.upload(uploadInput, fileList); expect(mockOnImageFileChange).toHaveBeenCalledWith(fileList[0]); expect(mockOnImageUrlChange).toHaveBeenCalledWith(''); expect(screen.getByText('test.png')).toBeInTheDocument(); expect(screen.getByAltText('Test Image')).toBeInTheDocument(); }); it('handles file removal after selection', async () => { const user = userEvent.setup(); renderWithIntl( , ); const fileList = [ new File(['dummy content'], 'test.png', { type: 'image/png' }), ]; const uploadInput = document.querySelector( 'input[type="file"]', ) as HTMLInputElement; await user.upload(uploadInput, fileList); // The trash icon for removing the selected file is in the component const removeButtons = screen.getAllByRole('button'); // Using custom role or closest identifier to find the remove unsubmitted file button // It's the button inside the div with selectedFileName const removeSelectedFileBtn = removeButtons[removeButtons.length - 1]; await user.click(removeSelectedFileBtn); expect(mockOnImageFileChange).toHaveBeenCalledWith(undefined); expect(screen.queryByText('test.png')).not.toBeInTheDocument(); }); it('handles image removal for existing image', async () => { const user = userEvent.setup(); renderWithIntl( , ); const removeBtn = screen.getByText('Remove Current'); await user.click(removeBtn); expect(mockOnDeleteImage).toHaveBeenCalled(); }); it('opens and closes Unsplash search mode', async () => { const user = userEvent.setup(); renderWithIntl( , ); const searchBtn = screen.getByText('Search Unsplash'); await user.click(searchBtn); expect( screen.getByPlaceholderText( 'Search high-quality images from Unsplash...', ), ).toBeInTheDocument(); expect(screen.getByText('Cancel Search')).toBeInTheDocument(); const cancelBtn = screen.getByText('Cancel Search'); await user.click(cancelBtn); expect( screen.queryByPlaceholderText( 'Search high-quality images from Unsplash...', ), ).not.toBeInTheDocument(); }); it('fetches and displays Unsplash images', async () => { const mockUnsplashResponse = { results: [ { id: '1', description: 'A beautiful sunset', urls: { thumb: 'https://thumb-url', regular: 'https://regular-url' }, }, ], }; global.fetch = vi.fn().mockResolvedValue({ ok: true, json: vi.fn().mockResolvedValue(mockUnsplashResponse), }); const user = userEvent.setup(); renderWithIntl( , ); await user.click(screen.getByText('Search Unsplash')); const input = screen.getByPlaceholderText( 'Search high-quality images from Unsplash...', ); // Type and press Enter (we trigger fetch directly by default in the component) await user.type(input, 'sunset{enter}'); await waitFor(() => { expect(global.fetch).toHaveBeenCalledWith( expect.stringContaining('keyword=sunset'), ); expect(screen.getByAltText('A beautiful sunset')).toBeInTheDocument(); }); }); it('handles Unsplash image selection', async () => { const mockUnsplashResponse = { results: [ { id: '1', description: 'A beautiful sunset', urls: { thumb: 'https://thumb-url', regular: 'https://regular-url' }, }, ], }; global.fetch = vi.fn().mockResolvedValue({ ok: true, json: vi.fn().mockResolvedValue(mockUnsplashResponse), }); const user = userEvent.setup(); renderWithIntl( , ); await user.click(screen.getByText('Search Unsplash')); const input = screen.getByPlaceholderText( 'Search high-quality images from Unsplash...', ); await user.type(input, 'sunset{enter}'); await waitFor(() => { expect(screen.getByAltText('A beautiful sunset')).toBeInTheDocument(); }); const imageBtn = screen .getByAltText('A beautiful sunset') .closest('button'); if (imageBtn) { await user.click(imageBtn); } expect(mockOnImageUrlChange).toHaveBeenCalledWith('https://regular-url'); // Ensure it correctly updates the UI expect(mockOnImageFileChange).toHaveBeenCalledWith(undefined); }); });