Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions domains/games/apps/1d4_web/src/__tests__/IndexView.test.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -141,17 +141,53 @@ 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.
expect(silentRow.cells[4]).toHaveTextContent('—');
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(() =>
Expand Down
35 changes: 29 additions & 6 deletions domains/games/apps/1d4_web/src/views/IndexView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export default function IndexView() {
const [endMonth, setEndMonth] = useState(thisMonth);
const [excludeBullet, setExcludeBullet] = useState(true);

const { data: requests = [] } = useQuery<IndexRequest[]>({
const [showPruned, setShowPruned] = useState(false);

const { data: allRequests = [] } = useQuery<IndexRequest[]>({
queryKey: ['indexRequests'],
queryFn: listIndexRequests,
refetchInterval: (query) =>
Expand All @@ -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<typeof createIndex>[0]) => createIndex(body),
onSuccess: () => {
Expand Down Expand Up @@ -159,8 +167,22 @@ export default function IndexView() {

<div className="panel">
<h2>Request status</h2>
{pruned.length > 0 && (
<button
type="button"
className="btn row-toggle"
aria-pressed={showPruned}
onClick={() => setShowPruned((v) => !v)}
>
{showPruned ? 'Hide pruned' : `Show ${pruned.length} pruned`}
</button>
)}
{requests.length === 0 ? (
<p className="empty">No recent requests. Submit a request above.</p>
<p className="empty">
{allRequests.length === 0
? 'No recent requests. Submit a request above.'
: 'Every recent request has been pruned.'}
</p>
) : (
<div className="table-wrap">
<table className="request-status-table">
Expand Down Expand Up @@ -205,11 +227,12 @@ export default function IndexView() {
</table>
</div>
)}
{requests.length > 0 && (
{allRequests.length > 0 && (
<p className="panel-note">
Indexed games are kept for 7 days, then deleted. A request stays in this list
after that, marked <strong>Pruned</strong> — 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 <strong>Pruned</strong> 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.
</p>
)}
</div>
Expand Down
Loading