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.
This commit is contained in:
kikootwo
2026-05-16 11:30:44 -04:00
parent 8bcfadc877
commit e39e44ee44
4 changed files with 57 additions and 1 deletions
+5
View File
@@ -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
}
-1
View File
@@ -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
/>
)}
@@ -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(
<AudiobookDetailsModal
asin="ASIN123"
isOpen={true}
onClose={vi.fn()}
requestStatus="pending"
isAvailable={false}
/>
);
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();
});
});
@@ -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 <div data-testid="audiobook-details-modal" data-open={String(props.isOpen)} />;
},
}));
vi.mock('next/image', () => ({
__esModule: true,
default: (props: any) => <img {...props} />,
@@ -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(
<RequestCard
request={{
...baseRequest,
audiobook: { ...baseRequest.audiobook, audibleAsin: 'ASIN123' },
}}
/>
);
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();
});
});