Files
ReadMeABook/tests/app/admin/components/RequestActionsDropdown.test.tsx
T
kikootwo a97979358f Implement file hash-based library matching and remove fuzzy ASIN matching
Adds file hash-based matching for Audiobookshelf library items to ensure 100% accurate ASIN assignment for RMAB-organized content. Removes fuzzy matching from library availability checks, making all matching ASIN-only to eliminate false positives and race conditions. Updates database schema, processors, and matcher utilities; adds new tests and documentation for the new matching strategy. Removes obsolete scripts, Dockerfile, and related tests; updates docker-compose for test environments.
2026-01-28 11:42:00 -05:00

107 lines
3.5 KiB
TypeScript

/**
* Component: Request Actions Dropdown Tests
* Documentation: documentation/admin-features/request-deletion.md
*/
// @vitest-environment jsdom
import React from 'react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import { RequestActionsDropdown } from '@/app/admin/components/RequestActionsDropdown';
vi.mock('@/hooks/useSmartDropdownPosition', () => ({
useSmartDropdownPosition: () => ({
containerRef: { current: null },
dropdownRef: { current: null },
positionAbove: false,
style: { position: 'fixed', top: 0, left: 0, minWidth: 120 },
}),
}));
vi.mock('@/components/requests/InteractiveTorrentSearchModal', () => ({
InteractiveTorrentSearchModal: ({
isOpen,
audiobook,
}: {
isOpen: boolean;
audiobook: { title: string; author: string };
}) => (isOpen ? <div>Interactive search for {audiobook.title}</div> : null),
}));
describe('RequestActionsDropdown', () => {
it('exposes manual search, interactive search, cancel, and delete actions', async () => {
const onManualSearch = vi.fn().mockResolvedValue(undefined);
const onCancel = vi.fn().mockResolvedValue(undefined);
const onDelete = vi.fn();
vi.spyOn(window, 'confirm').mockReturnValue(true);
render(
<RequestActionsDropdown
request={{
requestId: 'req-1',
title: 'Pending Book',
author: 'Author',
status: 'pending',
}}
onManualSearch={onManualSearch}
onCancel={onCancel}
onDelete={onDelete}
/>
);
fireEvent.click(screen.getByTitle('Actions'));
expect(screen.getByText('Manual Search')).toBeInTheDocument();
expect(screen.getByText('Interactive Search')).toBeInTheDocument();
expect(screen.getByText('Cancel Request')).toBeInTheDocument();
expect(screen.getByText('Delete Request')).toBeInTheDocument();
fireEvent.click(screen.getByText('Manual Search'));
await waitFor(() => expect(onManualSearch).toHaveBeenCalledWith('req-1'));
fireEvent.click(screen.getByTitle('Actions'));
fireEvent.click(screen.getByText('Interactive Search'));
expect(screen.getByText('Interactive search for Pending Book')).toBeInTheDocument();
fireEvent.click(screen.getByTitle('Actions'));
fireEvent.click(screen.getByText('Cancel Request'));
await waitFor(() => expect(onCancel).toHaveBeenCalledWith('req-1'));
fireEvent.click(screen.getByTitle('Actions'));
fireEvent.click(screen.getByText('Delete Request'));
expect(onDelete).toHaveBeenCalledWith('req-1', 'Pending Book');
});
it('shows view source and ebook fetch when available', async () => {
const onFetchEbook = vi.fn().mockResolvedValue(undefined);
const onDelete = vi.fn();
render(
<RequestActionsDropdown
request={{
requestId: 'req-2',
title: 'Downloaded Book',
author: 'Author',
status: 'downloaded',
torrentUrl: 'https://example.com/torrent',
}}
onManualSearch={vi.fn().mockResolvedValue(undefined)}
onCancel={vi.fn().mockResolvedValue(undefined)}
onDelete={onDelete}
onFetchEbook={onFetchEbook}
ebookSidecarEnabled
/>
);
fireEvent.click(screen.getByTitle('Actions'));
expect(screen.getByText('View Source')).toBeInTheDocument();
expect(screen.getByText('Try to fetch Ebook')).toBeInTheDocument();
fireEvent.click(screen.getByText('Try to fetch Ebook'));
await waitFor(() => expect(onFetchEbook).toHaveBeenCalledWith('req-2'));
});
});