import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, waitFor } from '@testing-library/react'; import { userEvent } from '@testing-library/user-event'; import MetadataSearchDialog from '../../src/components/roms/MetadataSearchDialog'; import { EnrichedGame } from '../../src/types/rom'; const mockEnrichMetadata = vi.fn(); vi.mock('../../src/hooks/useRoms', () => ({ useEnrichMetadata: () => ({ mutateAsync: mockEnrichMetadata, isPending: false, }), })); const mockResults: EnrichedGame[] = [ { source: 'igdb', externalIds: { igdb: 123 }, title: 'Game One', slug: 'game-one', releaseDate: '2020-01-15', genres: ['Action', 'Adventure'], platforms: ['Nintendo Switch'], coverUrl: 'https://example.com/cover1.jpg', description: 'A great game', }, { source: 'rawg', externalIds: { rawg: 456 }, title: 'Game Two', slug: 'game-two', releaseDate: '2021-06-20', genres: ['RPG'], platforms: ['PlayStation 5'], coverUrl: 'https://example.com/cover2.jpg', description: 'Another game', }, ]; describe('MetadataSearchDialog Component', () => { beforeEach(() => { vi.clearAllMocks(); }); it('should not render when isOpen is false', () => { render( ); expect(screen.queryByText(/search metadata/i)).not.toBeInTheDocument(); }); it('should render when isOpen is true', () => { render( ); expect(screen.getByText(/search metadata/i)).toBeInTheDocument(); }); it('should have search input field', () => { render( ); expect(screen.getByPlaceholderText(/search game title/i)).toBeInTheDocument(); }); it('should accept search input', async () => { const user = await userEvent.setup(); render( ); const input = screen.getByPlaceholderText(/search game title/i) as HTMLInputElement; await user.type(input, 'Game One'); expect(input.value).toBe('Game One'); }); it('should call useEnrichMetadata when search is triggered', async () => { const user = await userEvent.setup(); mockEnrichMetadata.mockResolvedValue([mockResults[0]]); render( ); const input = screen.getByPlaceholderText(/search game title/i); const searchButton = screen.getByRole('button', { name: /search/i }); await user.type(input, 'Game One'); await user.click(searchButton); await waitFor(() => { expect(mockEnrichMetadata).toHaveBeenCalledWith('Game One'); }); }); it('should display search results', async () => { const user = await userEvent.setup(); mockEnrichMetadata.mockResolvedValue(mockResults); render( ); const input = screen.getByPlaceholderText(/search game title/i); const searchButton = screen.getByRole('button', { name: /search/i }); await user.type(input, 'Game'); await user.click(searchButton); await waitFor(() => { expect(screen.getByText('Game One')).toBeInTheDocument(); expect(screen.getByText('Game Two')).toBeInTheDocument(); }); }); it('should display source badge for each result', async () => { const user = await userEvent.setup(); mockEnrichMetadata.mockResolvedValue(mockResults); render( ); const input = screen.getByPlaceholderText(/search game title/i); const searchButton = screen.getByRole('button', { name: /search/i }); await user.type(input, 'Game'); await user.click(searchButton); await waitFor(() => { expect(screen.getByText('IGDB')).toBeInTheDocument(); expect(screen.getByText('RAWG')).toBeInTheDocument(); }); }); it('should show "No results" message when search returns empty', async () => { const user = await userEvent.setup(); mockEnrichMetadata.mockResolvedValue([]); render( ); const input = screen.getByPlaceholderText(/search game title/i); const searchButton = screen.getByRole('button', { name: /search/i }); await user.type(input, 'NonexistentGame'); await user.click(searchButton); await waitFor(() => { expect(screen.getByText(/no results found/i)).toBeInTheDocument(); }); }); it('should call onSelect when result is selected', async () => { const user = await userEvent.setup(); const onSelect = vi.fn(); mockEnrichMetadata.mockResolvedValue(mockResults); render( ); const input = screen.getByPlaceholderText(/search game title/i); const searchButton = screen.getByRole('button', { name: /search/i }); await user.type(input, 'Game'); await user.click(searchButton); await waitFor(() => { expect(screen.getByText('Game One')).toBeInTheDocument(); }); const selectButton = screen.getAllByRole('button', { name: /select/i })[0]; await user.click(selectButton); expect(onSelect).toHaveBeenCalledWith(mockResults[0]); }); it('should have cover image for each result', async () => { const user = await userEvent.setup(); mockEnrichMetadata.mockResolvedValue(mockResults); const { container } = render( ); const input = screen.getByPlaceholderText(/search game title/i); const searchButton = screen.getByRole('button', { name: /search/i }); await user.type(input, 'Game'); await user.click(searchButton); await waitFor(() => { const images = container.querySelectorAll('img'); expect(images.length).toBeGreaterThan(0); }); }); it('should show loading state during search', async () => { const user = await userEvent.setup(); render( ); const input = screen.getByPlaceholderText(/search game title/i); const searchButton = screen.getByRole('button', { name: /search/i }); await user.type(input, 'Game'); await user.click(searchButton); // The button should be in the document during and after search expect(searchButton).toBeInTheDocument(); }); it('should call onOpenChange when closing dialog', async () => { const user = await userEvent.setup(); const onOpenChange = vi.fn(); render( ); // Find and click close button const buttons = screen.getAllByRole('button'); const closeButton = buttons.find( (btn) => btn.getAttribute('aria-label')?.includes('close') || btn.textContent?.includes('✕') || btn.textContent?.includes('Cancel') ); if (closeButton) { await user.click(closeButton); expect(onOpenChange).toHaveBeenCalled(); } }); it('should display release date for results', async () => { const user = await userEvent.setup(); mockEnrichMetadata.mockResolvedValue(mockResults); render( ); const input = screen.getByPlaceholderText(/search game title/i); const searchButton = screen.getByRole('button', { name: /search/i }); await user.type(input, 'Game'); await user.click(searchButton); await waitFor(() => { expect(screen.getByText(/2020/)).toBeInTheDocument(); expect(screen.getByText(/2021/)).toBeInTheDocument(); }); }); });