diff --git a/domains/games/apps/1d4_web/src/__tests__/IndexView.test.tsx b/domains/games/apps/1d4_web/src/__tests__/IndexView.test.tsx index c24897f1..0893ac92 100644 --- a/domains/games/apps/1d4_web/src/__tests__/IndexView.test.tsx +++ b/domains/games/apps/1d4_web/src/__tests__/IndexView.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, screen, waitFor, within, fireEvent } from '@testing-library/react'; +import { cleanup, render, screen, waitFor, within, fireEvent } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import type { UserEvent } from '@testing-library/user-event'; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; @@ -141,10 +141,21 @@ describe('IndexView', () => { await waitFor(() => expect(screen.getByRole('table')).toBeInTheDocument()); // Scoped to the table: the explanatory note below it also says "Pruned". - const table = within(screen.getByRole('table')); + let table = within(screen.getByRole('table')); expect(table.getByText('Indexed')).toBeInTheDocument(); expect(table.getByText('3d left')).toBeInTheDocument(); + // The pruned request is hidden until asked for; the count says how many. + expect(table.queryByText('Pruned')).not.toBeInTheDocument(); + expect(screen.queryByText('gone')).not.toBeInTheDocument(); + const toggle = screen.getByRole('button', { name: 'Show 1 pruned' }); + expect(toggle).toHaveAttribute('aria-pressed', 'false'); + fireEvent.click(toggle); + table = within(screen.getByRole('table')); expect(table.getByText('Pruned')).toBeInTheDocument(); + expect(screen.getByText('gone')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Hide pruned' })).toHaveAttribute('aria-pressed', 'true'); + fireEvent.click(screen.getByRole('button', { name: 'Hide pruned' })); + expect(screen.queryByText('gone')).not.toBeInTheDocument(); const silentRow = screen.getByText('silent').closest('tr') as HTMLTableRowElement; // Data column is 5th; Error is 6th and also renders a dash, so index precisely. @@ -152,6 +163,31 @@ describe('IndexView', () => { expect(silentRow.cells[4].querySelector('.data-badge')).toBeNull(); }); + it('offers no toggle when nothing is pruned, and says so when everything is', async () => { + setup(); + await waitFor(() => expect(screen.getByRole('table')).toBeInTheDocument()); + expect(screen.queryByRole('button', { name: /pruned/ })).not.toBeInTheDocument(); + + cleanup(); + vi.mocked(api.listIndexRequests).mockResolvedValue([ + { + ...completedRequest, + id: 'gone', + player: 'gone', + data: { status: 'EXPIRED', monthsAvailable: 0, monthsTotal: 3 }, + }, + ]); + setup(); + await waitFor(() => + expect(screen.getByText('Every recent request has been pruned.')).toBeInTheDocument() + ); + expect(screen.queryByRole('table')).not.toBeInTheDocument(); + // The note still explains what pruned means, since the toggle is the only trace. + expect(screen.getByText(/kept for 7 days/)).toBeInTheDocument(); + fireEvent.click(screen.getByRole('button', { name: 'Show 1 pruned' })); + expect(screen.getByRole('table')).toBeInTheDocument(); + }); + it('explains both retention windows so a pruned row is not a mystery', async () => { setup(); await waitFor(() => diff --git a/domains/games/apps/1d4_web/src/views/IndexView.tsx b/domains/games/apps/1d4_web/src/views/IndexView.tsx index 3b742fcf..b93a825a 100644 --- a/domains/games/apps/1d4_web/src/views/IndexView.tsx +++ b/domains/games/apps/1d4_web/src/views/IndexView.tsx @@ -30,7 +30,9 @@ export default function IndexView() { const [endMonth, setEndMonth] = useState(thisMonth); const [excludeBullet, setExcludeBullet] = useState(true); - const { data: requests = [] } = useQuery({ + const [showPruned, setShowPruned] = useState(false); + + const { data: allRequests = [] } = useQuery({ queryKey: ['indexRequests'], queryFn: listIndexRequests, refetchInterval: (query) => @@ -41,6 +43,12 @@ export default function IndexView() { : false, }); + // A pruned request has nothing left to show but its own row; after a week + // they are most of the list. Hidden by default, one click away, counted + // so the click is not a leap of faith. + const pruned = allRequests.filter((r) => r.data?.status === 'EXPIRED'); + const requests = showPruned ? allRequests : allRequests.filter((r) => r.data?.status !== 'EXPIRED'); + const mutation = useMutation({ mutationFn: (body: Parameters[0]) => createIndex(body), onSuccess: () => { @@ -159,8 +167,22 @@ export default function IndexView() {

Request status

+ {pruned.length > 0 && ( + + )} {requests.length === 0 ? ( -

No recent requests. Submit a request above.

+

+ {allRequests.length === 0 + ? 'No recent requests. Submit a request above.' + : 'Every recent request has been pruned.'} +

) : (
@@ -205,11 +227,12 @@ export default function IndexView() {
)} - {requests.length > 0 && ( + {allRequests.length > 0 && (

- Indexed games are kept for 7 days, then deleted. A request stays in this list - after that, marked Pruned — re-run it to index the games again. - The request itself is removed after 30 days. + Indexed games are kept for 7 days, then deleted. A request whose games are gone + is marked Pruned and hidden here — show it to see what it + covered, and re-run it to index the games again. The request itself is removed + after 30 days.

)}