test(11-01): add failing ResultsList spec + extend tender-radar-api client (RED)
Extends tender-radar-api.ts with the Tender type, listTenders(params) and fetchCoverage() (plain fetch, credentials:'include', matching the established fetchSourceConfig pattern — TanStack Query is not installed). Adds the RED-first ResultsList.test.tsx: render items with title/buyerName/deadline/value, "keine Wertangabe" for null estimatedValue (D-05), and an empty-state message. ResultsList.tsx does not exist yet. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import { cleanup, render, screen, waitFor } from '@testing-library/react';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
/**
|
||||
* ResultsList.test — RED-first (TDD) proof for Task 3 (FILTER-01, UI-01/05).
|
||||
* Mirrors the SourceConfigForm.test.tsx pattern: mock the api client
|
||||
* module, render, assert via waitFor. next/navigation is mocked because
|
||||
* ResultsList reads/writes URL searchParams for filter + sort state
|
||||
* (Research: URL-Param-getriebener Client).
|
||||
*/
|
||||
|
||||
const mockListTenders = vi.fn();
|
||||
|
||||
vi.mock('@/lib/tender-radar-api', () => ({
|
||||
listTenders: (...args: unknown[]) => mockListTenders(...args),
|
||||
}));
|
||||
|
||||
const mockReplace = vi.fn();
|
||||
let mockSearchParams = new URLSearchParams();
|
||||
|
||||
vi.mock('next/navigation', () => ({
|
||||
useRouter: () => ({ replace: mockReplace }),
|
||||
useSearchParams: () => mockSearchParams,
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
mockListTenders.mockReset();
|
||||
mockReplace.mockReset();
|
||||
mockSearchParams = new URLSearchParams();
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('ResultsList', () => {
|
||||
it('fetches on mount and renders title, buyerName, deadline and value from listTenders items', async () => {
|
||||
mockListTenders.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
id: 't1',
|
||||
title: 'Sanierung Rathaus',
|
||||
buyerName: 'Stadt Musterhausen',
|
||||
deadlineAt: '2026-09-01T00:00:00.000Z',
|
||||
estimatedValue: '150000.00',
|
||||
publishedAt: '2026-07-01T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
limit: 20,
|
||||
});
|
||||
|
||||
const { ResultsList } = await import('./ResultsList');
|
||||
render(<ResultsList />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Sanierung Rathaus')).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText('Stadt Musterhausen')).toBeInTheDocument();
|
||||
expect(mockListTenders).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('shows "keine Wertangabe" for a row with estimatedValue null (D-05 graceful handling)', async () => {
|
||||
mockListTenders.mockResolvedValue({
|
||||
items: [
|
||||
{
|
||||
id: 't2',
|
||||
title: 'Straßenbau Projekt X',
|
||||
buyerName: 'Landkreis Beispiel',
|
||||
deadlineAt: null,
|
||||
estimatedValue: null,
|
||||
publishedAt: '2026-07-01T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
page: 1,
|
||||
limit: 20,
|
||||
});
|
||||
|
||||
const { ResultsList } = await import('./ResultsList');
|
||||
render(<ResultsList />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('keine Wertangabe')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders an empty-state message when listTenders returns no items', async () => {
|
||||
mockListTenders.mockResolvedValue({ items: [], total: 0, page: 1, limit: 20 });
|
||||
|
||||
const { ResultsList } = await import('./ResultsList');
|
||||
render(<ResultsList />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Keine Ausschreibungen gefunden/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user