/** * Component: Search Page Tests * Documentation: documentation/frontend/components.md */ // @vitest-environment jsdom import React from 'react'; import { act, fireEvent, render, screen } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { resetMockAuthState } from '../helpers/mock-auth'; import { resetMockRouter } from '../helpers/mock-next-navigation'; const loadMoreMock = vi.hoisted(() => vi.fn()); const useSearchMock = vi.hoisted(() => vi.fn()); const usePreferencesMock = vi.hoisted(() => ({ cardSize: 5, setCardSize: vi.fn(), squareCovers: false, setSquareCovers: vi.fn(), hideAvailable: false, setHideAvailable: vi.fn(), })); vi.mock('@/lib/hooks/useAudiobooks', () => ({ useSearch: useSearchMock, Audiobook: {}, })); vi.mock('@/contexts/PreferencesContext', () => ({ usePreferences: () => usePreferencesMock, })); vi.mock('@/components/auth/ProtectedRoute', () => ({ ProtectedRoute: ({ children }: { children: React.ReactNode }) => <>{children}, })); vi.mock('@/components/layout/Header', () => ({ Header: () =>
, })); vi.mock('@/components/audiobooks/AudiobookGrid', () => ({ AudiobookGrid: ({ audiobooks, emptyMessage, cardSize, }: { audiobooks: any[]; emptyMessage: string; cardSize?: number; }) => (
{emptyMessage}
), })); vi.mock('@/components/ui/SectionToolbar', () => ({ SectionToolbar: () =>
, })); vi.mock('@/components/ui/LoadMoreBar', () => ({ LoadMoreBar: ({ hasMore, isLoading, onLoadMore, }: { loadedCount: number; totalCount?: number; hasMore: boolean; isLoading: boolean; onLoadMore: () => void; itemLabel?: string; }) => hasMore ? ( ) : (
All loaded
), })); describe('SearchPage', () => { beforeEach(() => { resetMockAuthState(); resetMockRouter(); useSearchMock.mockReset(); loadMoreMock.mockReset(); usePreferencesMock.cardSize = 5; usePreferencesMock.setCardSize.mockReset(); vi.useFakeTimers(); vi.resetModules(); }); afterEach(() => { vi.useRealTimers(); }); it('shows the empty state before a search query is entered', async () => { useSearchMock.mockReturnValue({ results: [], totalResults: 0, hasMore: false, isLoading: false, isLoadingMore: false, loadMore: loadMoreMock, }); const { default: SearchPage } = await import('@/app/search/page'); render(); expect(screen.getByText('Start typing to search for audiobooks')).toBeInTheDocument(); expect(useSearchMock).toHaveBeenCalledWith(''); }); it('debounces search input and loads more results', async () => { useSearchMock.mockReturnValue({ results: [{ asin: 'a1', title: 'Book One', author: 'Author' }], totalResults: 2, hasMore: true, isLoading: false, isLoadingMore: false, loadMore: loadMoreMock, }); const { default: SearchPage } = await import('@/app/search/page'); render(); const input = screen.getByPlaceholderText('Search by title, author, or narrator...'); fireEvent.change(input, { target: { value: 'Dune' } }); await act(async () => { vi.advanceTimersByTime(500); }); expect(screen.getByText('Search Results')).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Load more' })).toBeInTheDocument(); expect(screen.getByTestId('grid')).toHaveAttribute('data-count', '1'); fireEvent.click(screen.getByRole('button', { name: 'Load more' })); expect(loadMoreMock).toHaveBeenCalled(); }); });