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:
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user