fix(frontend): restore CTE card padding and hierarchy
This commit is contained in:
@@ -23,6 +23,11 @@ const plan: CtePlanV2 = {
|
||||
description: "Flag attivo",
|
||||
rationale: "Solo pazienti in follow-up",
|
||||
},
|
||||
{
|
||||
column: "pazienti.data_ultimo_follow_up",
|
||||
op: ">=",
|
||||
value: "CURRENT_DATE - INTERVAL '5 years'",
|
||||
},
|
||||
],
|
||||
output_columns: ["paziente_id", "data_nascita"],
|
||||
},
|
||||
@@ -40,12 +45,31 @@ const plan: CtePlanV2 = {
|
||||
test("renders strategy, question, and a chain strip with CTE names in order", () => {
|
||||
render(<CtePlanViewer plan={plan} />);
|
||||
|
||||
expect(screen.getByText("Question")).toBeInTheDocument();
|
||||
expect(screen.getByText("Strategy")).toBeInTheDocument();
|
||||
expect(screen.getByText("Execution order")).toBeInTheDocument();
|
||||
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("uses semantic CTE headings and explicit responsive card padding", () => {
|
||||
const { container } = render(<CtePlanViewer plan={plan} />);
|
||||
|
||||
expect(screen.getByRole("heading", { level: 3, name: "pazienti_idonei" })).toBeInTheDocument();
|
||||
|
||||
for (const header of container.querySelectorAll('[data-slot="card-header"]')) {
|
||||
expect(header).toHaveClass("p-4", "sm:p-5");
|
||||
}
|
||||
for (const content of container.querySelectorAll('[data-slot="card-content"]')) {
|
||||
expect(content).toHaveClass("p-4", "sm:p-5");
|
||||
}
|
||||
for (const card of container.querySelectorAll('[data-slot="card"]')) {
|
||||
expect(card).toHaveClass("shadow-none");
|
||||
}
|
||||
});
|
||||
|
||||
test("renders one card per CTE with index badge, purpose, and filter value", () => {
|
||||
render(<CtePlanViewer plan={plan} />);
|
||||
|
||||
@@ -68,6 +92,28 @@ test("renders each filter as aligned column, operator, and value fields", () =>
|
||||
expect(within(filter).getByText("TRUE")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("groups filters in one bordered divided list with accessible padded rows", () => {
|
||||
render(<CtePlanViewer plan={plan} />);
|
||||
|
||||
const filters = screen.getAllByRole("group", { name: /Filter \d/ });
|
||||
expect(filters).toHaveLength(2);
|
||||
expect(filters[0].parentElement).toBe(filters[1].parentElement);
|
||||
expect(filters[0].parentElement).toHaveClass("border", "divide-y");
|
||||
for (const filter of filters) {
|
||||
expect(filter).toHaveClass("p-3", "sm:p-4");
|
||||
}
|
||||
});
|
||||
|
||||
test("renders rationale as a plain top-divided section", () => {
|
||||
render(<CtePlanViewer plan={plan} />);
|
||||
|
||||
const rationale = screen
|
||||
.getByText("Base della catena: riduce il volume prima dei join")
|
||||
.closest("section");
|
||||
expect(rationale).toHaveClass("border-t", "pt-4");
|
||||
expect(rationale).not.toHaveClass("border-l-2", "bg-primary/5", "rounded-lg");
|
||||
});
|
||||
|
||||
test("shows 'no dependencies' for a CTE with an empty depends_on", () => {
|
||||
render(<CtePlanViewer plan={plan} />);
|
||||
expect(screen.getByText(/no dependencies/i)).toBeInTheDocument();
|
||||
@@ -81,8 +127,69 @@ test("renders depends_on chips for a CTE that has dependencies", () => {
|
||||
});
|
||||
|
||||
test("omits sections for missing optional fields without rendering 'undefined'", () => {
|
||||
render(<CtePlanViewer plan={plan} />);
|
||||
const minimalPlan: CtePlanV2 = {
|
||||
schema_version: 2,
|
||||
ctes: [{ name: "minimal", index: 1 }],
|
||||
};
|
||||
|
||||
render(<CtePlanViewer plan={minimalPlan} />);
|
||||
|
||||
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);
|
||||
expect(screen.queryByText("Question")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Strategy")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Purpose")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Tables")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Filters")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Keys")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Output columns")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Rationale")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("allows long CTE, table, filter, and chip values to wrap safely", () => {
|
||||
const longName = "cte_" + "extremely_long_unbroken_name_".repeat(6);
|
||||
const longTable = "schema." + "extremely_long_table_name_".repeat(6);
|
||||
const longColumn = "schema.table." + "extremely_long_filter_column_".repeat(5);
|
||||
const longOperator = "EXTREMELY_LONG_OPERATOR_WITHOUT_BREAKS";
|
||||
const longValue = "value_" + "extremely_long_unbroken_value_".repeat(6);
|
||||
const longChip = "column_" + "extremely_long_unbroken_chip_".repeat(6);
|
||||
const longPlan: CtePlanV2 = {
|
||||
schema_version: 2,
|
||||
ctes: [
|
||||
{
|
||||
name: longName,
|
||||
index: 1,
|
||||
depends_on: [longChip],
|
||||
tables: [{ name: longTable }],
|
||||
keys: [longChip],
|
||||
filters: [{ column: longColumn, op: longOperator, value: longValue }],
|
||||
output_columns: [longChip],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
render(<CtePlanViewer plan={longPlan} />);
|
||||
|
||||
expect(screen.getByRole("heading", { level: 3, name: longName })).toHaveClass(
|
||||
"min-w-0",
|
||||
"[overflow-wrap:anywhere]"
|
||||
);
|
||||
expect(screen.getByText(longTable)).toHaveClass(
|
||||
"min-w-0",
|
||||
"whitespace-pre-wrap",
|
||||
"[overflow-wrap:anywhere]"
|
||||
);
|
||||
expect(screen.getByText(longColumn)).toHaveClass("min-w-0", "[overflow-wrap:anywhere]");
|
||||
expect(screen.getByText(longOperator)).toHaveClass("min-w-0", "[overflow-wrap:anywhere]");
|
||||
expect(screen.getByText(longValue)).toHaveClass(
|
||||
"min-w-0",
|
||||
"whitespace-pre-wrap",
|
||||
"[overflow-wrap:anywhere]"
|
||||
);
|
||||
for (const chip of screen.getAllByText(longChip)) {
|
||||
expect(chip).toHaveClass(
|
||||
"min-w-0",
|
||||
"whitespace-pre-wrap",
|
||||
"[overflow-wrap:anywhere]"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user