From e39e44ee4421b299a5d866b90e49df0ad8f7a5a9 Mon Sep 17 00:00:00 2001 From: kikootwo Date: Sat, 16 May 2026 11:30:44 -0400 Subject: [PATCH] Add modal props & update RequestCard/tests Extend AudiobookDetailsModal props with onStatusChange, onIgnoreChange, hideRequestActions, hasReportedIssue, and aiReason. Stop forcing hideRequestActions when opening the modal from RequestCard so the modal can control whether request actions are shown. Add tests: verify admin sticky footer/status pill in AudiobookDetailsModal for pending requests, and add a RequestCard test that mocks AudiobookDetailsModal to assert the modal receives isOpen, asin and that hideRequestActions is not forced. Reset the new mock between tests. --- documentation/frontend/components.md | 5 ++++ src/components/requests/RequestCard.tsx | 1 - .../audiobooks/AudiobookDetailsModal.test.tsx | 22 ++++++++++++++ .../components/requests/RequestCard.test.tsx | 30 +++++++++++++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) diff --git a/documentation/frontend/components.md b/documentation/frontend/components.md index ccc8fa3..2299eab 100644 --- a/documentation/frontend/components.md +++ b/documentation/frontend/components.md @@ -109,10 +109,15 @@ interface AudiobookDetailsModalProps { isOpen: boolean; onClose: () => void; onRequestSuccess?: () => void; + onStatusChange?: (newStatus: string) => void; + onIgnoreChange?: (isIgnored: boolean) => void; isRequested?: boolean; requestStatus?: string | null; isAvailable?: boolean; requestedByUsername?: string | null; + hideRequestActions?: boolean; // Hides sticky action bar for read-only contexts (BookDate, ShelvesSection) + hasReportedIssue?: boolean; + aiReason?: string | null; adminActions?: React.ReactNode; // Optional admin buttons (Approve/Search/Deny) rendered as second row in action bar } diff --git a/src/components/requests/RequestCard.tsx b/src/components/requests/RequestCard.tsx index 0295881..15764d3 100644 --- a/src/components/requests/RequestCard.tsx +++ b/src/components/requests/RequestCard.tsx @@ -273,7 +273,6 @@ export function RequestCard({ request, showActions = true }: RequestCardProps) { onClose={() => setShowDetailsModal(false)} requestStatus={request.status} isAvailable={COMPLETED_STATUSES.includes(request.status as typeof COMPLETED_STATUSES[number])} - hideRequestActions /> )} diff --git a/tests/components/audiobooks/AudiobookDetailsModal.test.tsx b/tests/components/audiobooks/AudiobookDetailsModal.test.tsx index 04eebc0..63d5712 100644 --- a/tests/components/audiobooks/AudiobookDetailsModal.test.tsx +++ b/tests/components/audiobooks/AudiobookDetailsModal.test.tsx @@ -305,4 +305,26 @@ describe('AudiobookDetailsModal', () => { expect(screen.queryByText('Request failed')).toBeNull(); }); + + it('renders sticky footer with status pill and admin icons when opened from a pending request', async () => { + useAuthMock.mockReturnValue({ user: { id: 'admin-1', username: 'admin', role: 'admin' } }); + const { AudiobookDetailsModal } = await import('@/components/audiobooks/AudiobookDetailsModal'); + + render( + + ); + + await act(async () => {}); + + const statusPill = screen.getByRole('button', { name: 'Requested' }); + expect(statusPill).toBeDisabled(); + expect(screen.getByTitle('Interactive Search')).toBeInTheDocument(); + expect(screen.getByTitle('Manual Import')).toBeInTheDocument(); + }); }); diff --git a/tests/components/requests/RequestCard.test.tsx b/tests/components/requests/RequestCard.test.tsx index 9002f27..d34d592 100644 --- a/tests/components/requests/RequestCard.test.tsx +++ b/tests/components/requests/RequestCard.test.tsx @@ -10,11 +10,19 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; const cancelRequestMock = vi.hoisted(() => vi.fn()); +const detailsModalSpy = vi.hoisted(() => vi.fn()); vi.mock('@/lib/hooks/useRequests', () => ({ useCancelRequest: () => ({ cancelRequest: cancelRequestMock, isLoading: false }), })); +vi.mock('@/components/audiobooks/AudiobookDetailsModal', () => ({ + AudiobookDetailsModal: (props: any) => { + detailsModalSpy(props); + return
; + }, +})); + vi.mock('next/image', () => ({ __esModule: true, default: (props: any) => , @@ -52,6 +60,7 @@ const baseRequest = { describe('RequestCard', () => { beforeEach(() => { cancelRequestMock.mockReset(); + detailsModalSpy.mockReset(); }); afterEach(() => { @@ -218,4 +227,25 @@ describe('RequestCard', () => { expect(screen.queryByText(/^Releases /)).toBeNull(); }); + + it('opens AudiobookDetailsModal without hiding request actions when details are viewed', async () => { + const { RequestCard } = await import('@/components/requests/RequestCard'); + + render( + + ); + + fireEvent.click(screen.getByRole('button', { name: baseRequest.audiobook.title })); + + expect(detailsModalSpy).toHaveBeenCalled(); + const props = detailsModalSpy.mock.calls.at(-1)?.[0]; + expect(props.isOpen).toBe(true); + expect(props.asin).toBe('ASIN123'); + expect(props.hideRequestActions).toBeUndefined(); + }); });