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,76 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { CtePlanViewer } from "./CtePlanViewer";
|
||||
import type { CtePlanV2 } from "./artifactV2";
|
||||
|
||||
const plan: CtePlanV2 = {
|
||||
schema_version: 2,
|
||||
question: "Quanti pazienti hanno avuto un impianto negli ultimi 5 anni?",
|
||||
strategy: "Filtriamo prima i pazienti idonei, poi joiniamo gli impianti.",
|
||||
ctes: [
|
||||
{
|
||||
name: "pazienti_idonei",
|
||||
index: 1,
|
||||
purpose: "Seleziona i pazienti con follow-up attivo",
|
||||
rationale: "Base della catena: riduce il volume prima dei join",
|
||||
depends_on: [],
|
||||
tables: [{ name: "pazienti", description: "Anagrafica pazienti" }],
|
||||
keys: ["paziente_id"],
|
||||
filters: [
|
||||
{
|
||||
column: "pazienti.stato",
|
||||
op: "IS",
|
||||
value: "TRUE",
|
||||
description: "Flag attivo",
|
||||
rationale: "Solo pazienti in follow-up",
|
||||
},
|
||||
],
|
||||
output_columns: ["paziente_id", "data_nascita"],
|
||||
},
|
||||
{
|
||||
name: "impianti_recenti",
|
||||
index: 2,
|
||||
purpose: "Filtra gli impianti recenti",
|
||||
depends_on: ["pazienti_idonei"],
|
||||
tables: [{ name: "impianti" }],
|
||||
output_columns: ["impianto_id"],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
test("renders strategy, question, and a chain strip with CTE names in order", () => {
|
||||
render(<CtePlanViewer plan={plan} />);
|
||||
|
||||
expect(screen.getByText(plan.question!)).toBeInTheDocument();
|
||||
expect(screen.getByText(plan.strategy!)).toBeInTheDocument();
|
||||
expect(screen.getAllByText("pazienti_idonei").length).toBeGreaterThan(0);
|
||||
expect(screen.getAllByText("impianti_recenti").length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test("renders one card per CTE with index badge, purpose, and filter value", () => {
|
||||
render(<CtePlanViewer plan={plan} />);
|
||||
|
||||
expect(screen.getByText("CTE 1/2")).toBeInTheDocument();
|
||||
expect(screen.getByText("CTE 2/2")).toBeInTheDocument();
|
||||
expect(screen.getByText("Seleziona i pazienti con follow-up attivo")).toBeInTheDocument();
|
||||
expect(screen.getByText(/pazienti\.stato/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/TRUE/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("shows 'no dependencies' for a CTE with an empty depends_on", () => {
|
||||
render(<CtePlanViewer plan={plan} />);
|
||||
expect(screen.getByText(/no dependencies/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("renders depends_on chips for a CTE that has dependencies", () => {
|
||||
render(<CtePlanViewer plan={plan} />);
|
||||
// "pazienti_idonei" appears both as chain-strip entry and as a depends_on chip
|
||||
// for impianti_recenti — assert at least one occurrence beyond the strip.
|
||||
expect(screen.getAllByText("pazienti_idonei").length).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
test("omits sections for missing optional fields without rendering 'undefined'", () => {
|
||||
render(<CtePlanViewer plan={plan} />);
|
||||
expect(screen.queryByText(/undefined/i)).not.toBeInTheDocument();
|
||||
// impianti_recenti has no rationale/keys/filters — no crash, no stray text
|
||||
expect(screen.getAllByText("impianti_recenti").length).toBeGreaterThan(0);
|
||||
});
|
||||
Reference in New Issue
Block a user