feat(frontend): SQL/CTE viewer with shiki highlighting
TDD: SqlViewer renders collapsible CTE blocks with name/field-count/ test-status badges, shiki-highlighted SQL body (dangerouslySetInnerHTML), per-field comments, and a vertical/horizontal layout toggle. highlight.ts wraps shiki as a lazy singleton; tests mock it for determinism (pattern mirrors mermaid.ts). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { SqlViewer } from "./SqlViewer";
|
||||
|
||||
vi.mock("./highlight", () => ({
|
||||
highlightSql: vi.fn().mockResolvedValue('<pre data-testid="hl">SELECT 1</pre>'),
|
||||
}));
|
||||
|
||||
const blocks = [
|
||||
{
|
||||
name: "base_orders",
|
||||
fields: 5,
|
||||
testStatus: "passed" as const,
|
||||
sql: "SELECT id, amount FROM orders",
|
||||
comments: { amount: "importo lordo" },
|
||||
},
|
||||
{
|
||||
name: "final_query",
|
||||
fields: 3,
|
||||
testStatus: "failed" as const,
|
||||
sql: "SELECT * FROM base_orders",
|
||||
},
|
||||
];
|
||||
|
||||
test("(a) renders headers with name, field count, and test-status badge", async () => {
|
||||
render(<SqlViewer blocks={blocks} />);
|
||||
// Headers should be visible immediately
|
||||
expect(screen.getByText("base_orders")).toBeInTheDocument();
|
||||
expect(screen.getByText("final_query")).toBeInTheDocument();
|
||||
|
||||
// Field counts
|
||||
expect(screen.getByText(/5 campi/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/3 campi/i)).toBeInTheDocument();
|
||||
|
||||
// Test-status badges
|
||||
expect(screen.getByText(/passed/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/failed/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("(b) bodies start expanded and clicking header collapses then re-expands", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<SqlViewer blocks={blocks} />);
|
||||
|
||||
// Both highlighted bodies should appear after async highlight resolves
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByTestId("hl")).toHaveLength(2);
|
||||
});
|
||||
|
||||
// Collapse the first block by clicking its header
|
||||
const firstHeader = screen.getByRole("button", { name: /base_orders/i });
|
||||
await user.click(firstHeader);
|
||||
|
||||
// Now only one highlighted block visible
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByTestId("hl")).toHaveLength(1);
|
||||
});
|
||||
|
||||
// Re-expand
|
||||
await user.click(firstHeader);
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByTestId("hl")).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
test("(c) layout toggle switches between vertical and horizontal", async () => {
|
||||
render(<SqlViewer blocks={blocks} />);
|
||||
|
||||
const toggle = screen.getByRole("button", { name: /orizzontale|verticale/i });
|
||||
expect(toggle).toBeInTheDocument();
|
||||
await userEvent.click(toggle);
|
||||
// After click the label should flip
|
||||
expect(
|
||||
screen.getByRole("button", { name: /orizzontale|verticale/i })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
Reference in New Issue
Block a user