test(11-04): add failing test for TenderDetail component

Covers the three must-have truths: source link with safe target/rel,
graceful NULL placeholders for value/deadline, and the mandatory
no-local-mirroring notice for Vergabeunterlagen (rawPayload is 100%
NULL in the live DB per research - no document URLs exist to mirror).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-21 16:20:28 +02:00
parent acd7e55094
commit 3cc7194d9e
@@ -0,0 +1,125 @@
import { cleanup, render, screen, waitFor } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
/**
* TenderDetail.test — RED-first (TDD) proof for Plan 11-04 (UI-02, D-07).
* Mirrors the SourceConfigForm.test.tsx pattern: mock the api client
* module (getTender), render with a tenderId prop, assert via waitFor.
*
* Covers the three must-have truths from the plan:
* (a) a source link is rendered and points at the tender's sourceUrl
* with a safe target/rel combination (T-11-09).
* (b) estimatedValue=null and deadlineAt=null render graceful German
* placeholders instead of blank/error output (D-05 applies equally
* to the detail view, not just the list).
* (c) the mandatory no-local-mirroring notice is always present — no
* Vergabeunterlagen are fetched or mirrored locally (rawPayload is
* 100% NULL in the live DB; research finding, no document URLs
* exist to mirror in the first place).
*/
const mockGetTender = vi.fn();
vi.mock('@/lib/tender-radar-api', () => ({
getTender: (...args: unknown[]) => mockGetTender(...args),
}));
afterEach(() => {
cleanup();
mockGetTender.mockReset();
vi.restoreAllMocks();
});
describe('TenderDetail', () => {
it('renders a source link pointing at the tender sourceUrl with safe target/rel', async () => {
mockGetTender.mockResolvedValue({
id: 't1',
sourcePortal: 'doe-opendata',
title: 'Sanierung Rathaus',
buyerName: 'Stadt Musterhausen',
cpvCodes: ['45000000'],
region: 'Oberbayern',
plz: '80331',
bundesland: 'Bayern',
deadlineAt: '2026-09-01T00:00:00.000Z',
estimatedValue: '150000.00',
procedureType: 'Offenes Verfahren',
status: 'active',
sourceUrl: 'https://example-portal.de/ausschreibung/t1',
publishedAt: '2026-07-01T00:00:00.000Z',
});
const { TenderDetail } = await import('./TenderDetail');
render(<TenderDetail tenderId="t1" onClose={() => {}} />);
await waitFor(() => {
expect(screen.getByText('Sanierung Rathaus')).toBeInTheDocument();
});
const link = screen.getByRole('link', { name: /Quellportal/i });
expect(link).toHaveAttribute(
'href',
'https://example-portal.de/ausschreibung/t1',
);
expect(link).toHaveAttribute('target', '_blank');
expect(link).toHaveAttribute('rel', 'noopener noreferrer');
expect(mockGetTender).toHaveBeenCalledWith('t1');
});
it('shows graceful placeholders when estimatedValue and deadlineAt are null', async () => {
mockGetTender.mockResolvedValue({
id: 't2',
sourcePortal: 'doe-opendata',
title: 'Straßenbau Projekt X',
buyerName: null,
cpvCodes: [],
region: null,
plz: null,
bundesland: null,
deadlineAt: null,
estimatedValue: null,
procedureType: null,
status: 'active',
sourceUrl: null,
publishedAt: '2026-07-01T00:00:00.000Z',
});
const { TenderDetail } = await import('./TenderDetail');
render(<TenderDetail tenderId="t2" onClose={() => {}} />);
await waitFor(() => {
expect(screen.getByText('Straßenbau Projekt X')).toBeInTheDocument();
});
expect(screen.getByText('keine Wertangabe')).toBeInTheDocument();
expect(screen.getByText('keine Frist angegeben')).toBeInTheDocument();
});
it('always shows the no-local-mirroring notice for Vergabeunterlagen', async () => {
mockGetTender.mockResolvedValue({
id: 't3',
sourcePortal: 'doe-opendata',
title: 'Beschaffung IT-Ausstattung',
buyerName: 'Landkreis Beispiel',
cpvCodes: ['30200000'],
region: null,
plz: null,
bundesland: null,
deadlineAt: '2026-08-15T00:00:00.000Z',
estimatedValue: '25000.00',
procedureType: 'Verhandlungsverfahren',
status: 'active',
sourceUrl: 'https://example-portal.de/ausschreibung/t3',
publishedAt: '2026-07-01T00:00:00.000Z',
});
const { TenderDetail } = await import('./TenderDetail');
render(<TenderDetail tenderId="t3" onClose={() => {}} />);
await waitFor(() => {
expect(screen.getByText('Beschaffung IT-Ausstattung')).toBeInTheDocument();
});
expect(
screen.getByText(/Vergabeunterlagen werden nicht lokal gespiegelt/i),
).toBeInTheDocument();
});
});