mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-02 20:30:10 +00:00
20c8fb0898
Introduce user-reported-issues and Goodreads shelf sync features and wire them into notifications. Adds Prisma migrations and schema changes (ReportedIssue, GoodreadsShelf, GoodreadsBookMapping), API endpoints for reporting (POST /audiobooks/[asin]/report-issue) and admin management (list, resolve/dismiss, replace), and an admin UI section to view/dismiss/replace reported issues. Adds a new notification event (issue_reported) with updates to notification schemas, docs and provider handling, plus a notification-events constants file. Refactors request creation to use createRequestForUser service, adds a Goodreads sync processor/service/hooks/UI modals, a scrape-resilience util, and related tests and minor integration updates.
126 lines
4.1 KiB
TypeScript
126 lines
4.1 KiB
TypeScript
/**
|
|
* Component: Profile Page Tests
|
|
* Documentation: documentation/frontend/components.md
|
|
*/
|
|
|
|
// @vitest-environment jsdom
|
|
|
|
import React from 'react';
|
|
import { render, screen } from '@testing-library/react';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { resetMockAuthState, setMockAuthState } from '../helpers/mock-auth';
|
|
import { resetMockRouter } from '../helpers/mock-next-navigation';
|
|
|
|
const useRequestsMock = vi.hoisted(() => vi.fn());
|
|
|
|
vi.mock('@/lib/hooks/useRequests', () => ({
|
|
useRequests: useRequestsMock,
|
|
}));
|
|
|
|
vi.mock('@/components/layout/Header', () => ({
|
|
Header: () => <div data-testid="header" />,
|
|
}));
|
|
|
|
vi.mock('@/components/requests/RequestCard', () => ({
|
|
RequestCard: ({ request, showActions }: { request: any; showActions?: boolean }) => (
|
|
<div
|
|
data-testid="request-card"
|
|
data-request-id={request.id}
|
|
data-show-actions={String(!!showActions)}
|
|
>
|
|
{request.id}
|
|
</div>
|
|
),
|
|
}));
|
|
|
|
const getStatValue = (label: string) => {
|
|
const labelNode = screen.getByText(label);
|
|
const container = labelNode.parentElement;
|
|
const valueNode = container?.querySelector('div:first-child');
|
|
return valueNode?.textContent;
|
|
};
|
|
|
|
describe('ProfilePage', () => {
|
|
beforeEach(() => {
|
|
resetMockAuthState();
|
|
resetMockRouter();
|
|
useRequestsMock.mockReset();
|
|
vi.resetModules();
|
|
});
|
|
|
|
it('prompts for authentication when no user is available', async () => {
|
|
setMockAuthState({ user: null });
|
|
useRequestsMock.mockReturnValue({ requests: [], isLoading: false });
|
|
|
|
const { default: ProfilePage } = await import('@/app/profile/page');
|
|
render(<ProfilePage />);
|
|
|
|
expect(screen.getByText('Sign in required')).toBeInTheDocument();
|
|
expect(screen.getByText('Please log in to view your profile')).toBeInTheDocument();
|
|
});
|
|
|
|
it('calculates stats and orders recent requests', async () => {
|
|
setMockAuthState({
|
|
user: {
|
|
id: 'user-1',
|
|
plexId: 'plex-1',
|
|
username: 'user',
|
|
role: 'user',
|
|
},
|
|
isLoading: false,
|
|
});
|
|
|
|
const requests = [
|
|
{ id: 'req-1', status: 'pending', createdAt: '2025-01-01T10:00:00Z', audiobook: {} },
|
|
{ id: 'req-2', status: 'awaiting_search', createdAt: '2025-01-02T10:00:00Z', audiobook: {} },
|
|
{ id: 'req-3', status: 'available', createdAt: '2025-01-03T10:00:00Z', audiobook: {} },
|
|
{ id: 'req-4', status: 'failed', createdAt: '2025-01-04T10:00:00Z', audiobook: {} },
|
|
{ id: 'req-5', status: 'cancelled', createdAt: '2025-01-05T10:00:00Z', audiobook: {} },
|
|
{ id: 'req-6', status: 'searching', createdAt: '2025-01-06T10:00:00Z', audiobook: {} },
|
|
];
|
|
|
|
useRequestsMock.mockReturnValue({ requests, isLoading: false });
|
|
|
|
const { default: ProfilePage } = await import('@/app/profile/page');
|
|
render(<ProfilePage />);
|
|
|
|
expect(getStatValue('Total')).toBe('6');
|
|
expect(getStatValue('Active')).toBe('2');
|
|
expect(getStatValue('Waiting')).toBe('1');
|
|
expect(getStatValue('Complete')).toBe('1');
|
|
expect(getStatValue('Failed')).toBe('1');
|
|
expect(getStatValue('Cancelled')).toBe('1');
|
|
|
|
const cards = screen.getAllByTestId('request-card');
|
|
expect(cards).toHaveLength(5);
|
|
expect(cards[0]).toHaveAttribute('data-request-id', 'req-6');
|
|
});
|
|
|
|
it('shows active downloads when downloading requests exist', async () => {
|
|
setMockAuthState({
|
|
user: {
|
|
id: 'user-2',
|
|
plexId: 'plex-2',
|
|
username: 'download-user',
|
|
role: 'user',
|
|
},
|
|
isLoading: false,
|
|
});
|
|
|
|
const requests = [
|
|
{ id: 'req-downloading', status: 'downloading', createdAt: '2025-02-01T10:00:00Z', audiobook: {} },
|
|
{ id: 'req-processing', status: 'processing', createdAt: '2025-02-02T10:00:00Z', audiobook: {} },
|
|
{ id: 'req-pending', status: 'pending', createdAt: '2025-02-03T10:00:00Z', audiobook: {} },
|
|
];
|
|
|
|
useRequestsMock.mockReturnValue({ requests, isLoading: false });
|
|
|
|
const { default: ProfilePage } = await import('@/app/profile/page');
|
|
render(<ProfilePage />);
|
|
|
|
expect(screen.getByText('Active Downloads')).toBeInTheDocument();
|
|
const cards = screen.getAllByTestId('request-card');
|
|
expect(cards.length).toBeGreaterThan(0);
|
|
});
|
|
});
|