mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-03 04:40:09 +00:00
95e63dfc36
Introduce ROOTLESS_CONTAINER env to opt out of gosu (replace /proc uid_map detection) and update entrypoint messaging; adjust app-start.sh and redis-start.sh to skip gosu when ROOTLESS_CONTAINER=true and warn on UID/GID mismatch only when applicable. Backend: include audiobook audibleAsin in admin requests response (mapped to asin) and pass baseUrl through test-flaresolverr endpoint to the FlareSolverr tester. Frontend: RecentRequestsTable and RequestActionsDropdown now surface asin, accept/passthrough annasArchiveBaseUrl, and add a "View Details" flow using AudiobookDetailsModal; admin page passes ebook baseUrl from settings. InteractiveTorrentSearchModal refactor: improved UX/UI, keyboard handling, portal/modal mounting, skeleton/loading states, formatting helpers, and richer result display. Tests updated to match changes.
130 lines
4.4 KiB
TypeScript
130 lines
4.4 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('uses configured base URL for ebook View Source link', () => {
|
|
render(
|
|
<RequestActionsDropdown
|
|
request={{
|
|
requestId: 'req-ebook',
|
|
title: 'Ebook Title',
|
|
author: 'Author',
|
|
status: 'downloaded',
|
|
type: 'ebook',
|
|
torrentUrl: JSON.stringify(['https://annas-archive.li/slow_download/abc123def456abc123def456abc123de/0/5']),
|
|
}}
|
|
onManualSearch={vi.fn().mockResolvedValue(undefined)}
|
|
onCancel={vi.fn().mockResolvedValue(undefined)}
|
|
onDelete={vi.fn()}
|
|
annasArchiveBaseUrl="https://custom-mirror.org"
|
|
/>
|
|
);
|
|
|
|
fireEvent.click(screen.getByTitle('Actions'));
|
|
const viewSourceLink = screen.getByText('View Source').closest('a');
|
|
expect(viewSourceLink).toHaveAttribute('href', 'https://custom-mirror.org/md5/abc123def456abc123def456abc123de');
|
|
});
|
|
|
|
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('Grab Ebook')).toBeInTheDocument();
|
|
|
|
fireEvent.click(screen.getByText('Grab Ebook'));
|
|
await waitFor(() => expect(onFetchEbook).toHaveBeenCalledWith('req-2'));
|
|
});
|
|
});
|