feat(frontend): v2 artifact viewers for cte_plan/cte_result/phase
Add CtePlanViewer, CteResultViewer, and PhaseSummaryViewer to render the structured v2 payloads (schema_version: 2) the harness now emits for artifact-gate widgets, per contracts.md. Extract PreviewGrid from ResultsPanel as a reusable AG Grid component shared by CteResultViewer. ArtifactView dispatches to the new viewers on a schema_version/shape guard, falling back to the existing legacy renderers unchanged for older sessions and replay fixtures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
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("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();
|
||||
});
|
||||
Reference in New Issue
Block a user