Files
ThothII/frontend/src/viewers/PhaseSummaryViewer.test.tsx
T
marcopanandClaude Fable 5 b59b57c4e6 fix(review-gates): green success badges, null-safe preview rows, optional section items
- Extract shared statusBadgeClass() helper (src/viewers/statusBadge.ts) so
  SqlViewer, CteResultViewer and PhaseSummaryViewer can't drift: ok/success/
  passed/promoted render green (--success), warn renders amber (--warning),
  error/failed stay destructive red, everything else stays neutral outline.
  Previously CteResultViewer/PhaseSummaryViewer mapped "ok"/"promoted" to the
  default badge variant, which is bg-primary (GSD red) — success states
  rendered red.
- enrich.js: buildCteResultV2 now falls back preview.rows to [] instead of
  null when last_test.preview_rows is missing (pre-upgrade ok records), and
  CteResultViewer reads result.preview?.rows?.length with a null-safe
  fallback so it degrades to the "No preview rows" empty state instead of
  crashing.
- PhaseSummaryViewer: section.items is optional (model-authored sections can
  be prose-only); render (section.items ?? []) instead of crashing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 01:37:35 +02:00

103 lines
4.0 KiB
TypeScript

import { render, screen } from "@testing-library/react";
import { PhaseSummaryViewer } from "./PhaseSummaryViewer";
import type { PhaseSummaryV2 } from "./artifactV2";
const phase: PhaseSummaryV2 = {
schema_version: 2,
phase: { id: "F5", num: 5, name: "sintesi" },
summary: "## Recap\nLa catena copre pazienti e impianti.",
checks: [
{ label: "Schema linking completo", status: "ok" },
{ label: "Join validati", status: "warn", detail: "Un join manuale non testato" },
{ label: "SQL eseguibile", status: "fail", detail: "Errore di sintassi" },
],
sections: [
{
title: "Criteri di selezione",
items: [
{
label: "Pazienti attivi",
table: "pazienti",
column: "stato",
value: "IS TRUE",
kind: "filter",
description: "Flag di stato attivo",
rationale: "Solo pazienti in follow-up",
},
],
},
],
tables: [
{
name: "pazienti",
role: "promoted",
description: "Anagrafica pazienti",
columns: [{ name: "stato", description: "Flag attivo", value_filter: "IS TRUE" }],
},
{ name: "log_accessi", role: "excluded", description: "Non pertinente" },
],
open_questions: ["Serve confermare la finestra temporale?"],
};
test("renders phase id/name header and markdown summary", () => {
render(<PhaseSummaryViewer phase={phase} />);
expect(screen.getByText(/F5/)).toBeInTheDocument();
expect(screen.getByText(/sintesi/)).toBeInTheDocument();
expect(screen.getByText("Recap")).toBeInTheDocument();
expect(screen.getByText(/La catena copre pazienti e impianti/)).toBeInTheDocument();
});
test("renders checks with status label and detail", () => {
render(<PhaseSummaryViewer phase={phase} />);
expect(screen.getByText("Schema linking completo")).toBeInTheDocument();
expect(screen.getByText("Join validati")).toBeInTheDocument();
expect(screen.getByText("Un join manuale non testato")).toBeInTheDocument();
expect(screen.getByText("SQL eseguibile")).toBeInTheDocument();
expect(screen.getByText("Errore di sintassi")).toBeInTheDocument();
});
test("renders sections with item label, table.column code-chip, and value badge", () => {
render(<PhaseSummaryViewer phase={phase} />);
expect(screen.getByText("Criteri di selezione")).toBeInTheDocument();
expect(screen.getByText("Pazienti attivi")).toBeInTheDocument();
expect(screen.getByText("pazienti.stato")).toBeInTheDocument();
expect(screen.getAllByText("IS TRUE").length).toBeGreaterThan(0);
expect(screen.getByText("Flag di stato attivo")).toBeInTheDocument();
expect(screen.getByText("Solo pazienti in follow-up")).toBeInTheDocument();
});
test("renders tables recap with promoted/excluded roles and columns", () => {
render(<PhaseSummaryViewer phase={phase} />);
expect(screen.getByText("pazienti")).toBeInTheDocument();
expect(screen.getByText(/promoted/i)).toBeInTheDocument();
expect(screen.getByText("log_accessi")).toBeInTheDocument();
expect(screen.getByText(/excluded/i)).toBeInTheDocument();
expect(screen.getByText("stato")).toBeInTheDocument();
expect(screen.getAllByText("IS TRUE").length).toBeGreaterThan(0);
});
test("renders open_questions as a bullet list", () => {
render(<PhaseSummaryViewer phase={phase} />);
expect(screen.getByText("Serve confermare la finestra temporale?")).toBeInTheDocument();
});
test("renders a section with title only (no items key) without crashing", () => {
const proseOnly: PhaseSummaryV2 = {
schema_version: 2,
phase: { id: "F5", num: 5, name: "sintesi" },
sections: [{ title: "Note libere" }],
};
render(<PhaseSummaryViewer phase={proseOnly} />);
expect(screen.getByText("Note libere")).toBeInTheDocument();
});
test("omits open_questions section when empty and shows no 'undefined' text", () => {
const minimal: PhaseSummaryV2 = {
schema_version: 2,
phase: { id: "F1", num: 1, name: "domanda" },
};
render(<PhaseSummaryViewer phase={minimal} />);
expect(screen.queryByText(/undefined/i)).not.toBeInTheDocument();
expect(screen.getByText(/F1/)).toBeInTheDocument();
});