erDiagram reads data_type/type from the candidate (top-level or signals),
falling back to the generic 'col' token Mermaid needs — real types render as
soon as schema_linking.json carries them. ArtifactView gains a defensive
{content} fallback for sql-kind artifacts, verified against real session shapes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
113 lines
3.8 KiB
TypeScript
113 lines
3.8 KiB
TypeScript
import { render, screen, waitFor } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { SchemaLinkingViewer, buildErDiagram } from "./SchemaLinkingViewer";
|
|
|
|
vi.mock("./mermaid", () => ({
|
|
renderMermaid: vi.fn().mockResolvedValue('<svg data-testid="mm"></svg>'),
|
|
}));
|
|
|
|
const smallLinking = {
|
|
question: "quante ordini?",
|
|
candidates: [
|
|
{ kind: "table" as const, name: "orders", decision: "promoted" as const },
|
|
{ kind: "column" as const, name: "orders.id", decision: "promoted" as const },
|
|
{ kind: "table" as const, name: "users", decision: "excluded" as const },
|
|
],
|
|
joins: [{ from: "orders", to: "orders.id" }],
|
|
excluded: [{ kind: "table" as const, name: "users" }],
|
|
open_questions: [],
|
|
};
|
|
|
|
const bigLinking = {
|
|
question: "grossa query",
|
|
candidates: Array.from({ length: 46 }, (_, i) => ({
|
|
kind: "table" as const,
|
|
name: `table_${i}`,
|
|
decision: "promoted" as const,
|
|
})),
|
|
joins: [],
|
|
excluded: [],
|
|
open_questions: [],
|
|
};
|
|
|
|
test("(a) default renders the mermaid container (mocked svg appears)", async () => {
|
|
render(<SchemaLinkingViewer linking={smallLinking} />);
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId("mm")).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
test("(b) clicking toggle switches to table listing a promoted table name", async () => {
|
|
render(<SchemaLinkingViewer linking={smallLinking} />);
|
|
// wait for mermaid to load
|
|
await waitFor(() => expect(screen.getByTestId("mm")).toBeInTheDocument());
|
|
|
|
const toggle = screen.getByRole("button", { name: /table/i });
|
|
await userEvent.click(toggle);
|
|
|
|
expect(screen.getByText("orders")).toBeInTheDocument();
|
|
expect(screen.queryByTestId("mm")).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("(c) oversized linking (>45) shows cap notice", () => {
|
|
render(<SchemaLinkingViewer linking={bigLinking} />);
|
|
expect(
|
|
screen.getByText(/too many elements for the graph/i)
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
test("(d) ~10 promoted + ~50 excluded does NOT cap; graph is available", async () => {
|
|
const mixedLinking = {
|
|
question: "caso misto",
|
|
candidates: [
|
|
...Array.from({ length: 10 }, (_, i) => ({
|
|
kind: "table" as const,
|
|
name: `kept_${i}`,
|
|
decision: "promoted" as const,
|
|
})),
|
|
...Array.from({ length: 50 }, (_, i) => ({
|
|
kind: "table" as const,
|
|
name: `dropped_${i}`,
|
|
decision: "excluded" as const,
|
|
})),
|
|
],
|
|
joins: [],
|
|
excluded: [],
|
|
open_questions: [],
|
|
};
|
|
render(<SchemaLinkingViewer linking={mixedLinking} />);
|
|
expect(
|
|
screen.queryByText(/troppi elementi per il grafico/i)
|
|
).not.toBeInTheDocument();
|
|
// Grafico (mermaid) view is the default and renders the mocked svg.
|
|
await waitFor(() => expect(screen.getByTestId("mm")).toBeInTheDocument());
|
|
});
|
|
|
|
test("(e) buildErDiagram emits entities, attributes and a relation", () => {
|
|
const def = buildErDiagram(
|
|
[
|
|
{ kind: "table", name: "orders", decision: "promoted" },
|
|
{ kind: "column", name: "orders.id", decision: "promoted" },
|
|
{ kind: "table", name: "customers", decision: "promoted" },
|
|
],
|
|
[{ from: "orders.customer_id", to: "customers.id" }],
|
|
);
|
|
expect(def.startsWith("erDiagram")).toBe(true);
|
|
expect(def).toContain("orders {");
|
|
expect(def).toContain("col id");
|
|
expect(def).toContain("orders }o--o{ customers : join");
|
|
});
|
|
|
|
test("(f) buildErDiagram uses a column's real type when the data carries one", () => {
|
|
const def = buildErDiagram(
|
|
[
|
|
{ kind: "table", name: "orders", decision: "promoted" },
|
|
{ kind: "column", name: "orders.total", decision: "promoted", signals: { data_type: "numeric" } },
|
|
{ kind: "column", name: "orders.id", decision: "promoted" }, // no type -> generic "col"
|
|
],
|
|
[],
|
|
);
|
|
expect(def).toContain("numeric total");
|
|
expect(def).toContain("col id");
|
|
});
|