chore: commit remaining worktree changes

This commit is contained in:
2026-08-26 08:10:37 +02:00
parent ec061c42d4
commit f48196a57f
234 changed files with 146 additions and 61044 deletions
-145
View File
@@ -1,145 +0,0 @@
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { http, HttpResponse } from "msw";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { server } from "../test/msw";
import { ResultsPanel } from "./ResultsPanel";
const BASE = "/api";
function makeClient() {
return new QueryClient({
defaultOptions: { queries: { retry: false } },
});
}
function wrap(ui: React.ReactElement) {
return (
<QueryClientProvider client={makeClient()}>{ui}</QueryClientProvider>
);
}
// (a) 2-col × 2-row preview → AGGrid renders cell values
test("(a) renders grid cells for a 2×2 preview result", async () => {
server.use(
http.post(`${BASE}/sessions/sess-1/sql/preview`, () =>
HttpResponse.json({
columns: ["name", "age"],
rows: [
["Alice", 30],
["Bob", 25],
],
execution_ms: 5,
truncated: true,
limit: 10,
offset: 0,
})
)
);
render(wrap(<ResultsPanel sessionId="sess-1" />));
// Wait for data to load; check known cell values
await screen.findByText("Alice");
expect(screen.getByText("Bob")).toBeInTheDocument();
expect(screen.getByText("30")).toBeInTheDocument();
expect(screen.getByText("25")).toBeInTheDocument();
// Grid must be present, not bold scalar
expect(screen.queryByTestId("scalar-value")).not.toBeInTheDocument();
// truncated:true → show the "results truncated" indicator
expect(screen.getByText(/results truncated/i)).toBeInTheDocument();
});
// (b) 1×1 preview → bold scalar shown, no grid
test("(b) renders bold scalar for a 1×1 result", async () => {
server.use(
http.post(`${BASE}/sessions/sess-2/sql/preview`, () =>
HttpResponse.json({
columns: ["count"],
rows: [[42]],
execution_ms: 2,
truncated: false,
limit: 10,
offset: 0,
})
)
);
render(wrap(<ResultsPanel sessionId="sess-2" />));
// Should show the scalar value in bold
const bold = await screen.findByTestId("scalar-value");
expect(bold).toHaveTextContent("42");
// No grid
expect(screen.queryByRole("treegrid")).not.toBeInTheDocument();
expect(screen.queryByRole("grid")).not.toBeInTheDocument();
});
// (c) clicking "Esporta CSV" calls the export endpoint
test("(c) Esporta CSV button calls the export endpoint", async () => {
let exportCalled = false;
server.use(
http.post(`${BASE}/sessions/sess-3/sql/preview`, () =>
HttpResponse.json({
columns: ["id"],
rows: [[1], [2]],
execution_ms: 3,
truncated: false,
limit: 10,
offset: 0,
})
),
http.post(`${BASE}/sessions/sess-3/sql/export`, () => {
exportCalled = true;
return HttpResponse.json({ path: "/tmp/export.csv" });
})
);
const user = userEvent.setup();
render(wrap(<ResultsPanel sessionId="sess-3" />));
// Wait for preview to load (use role to avoid ambiguity with paging panel)
await waitFor(() => expect(screen.getAllByRole("gridcell")).not.toHaveLength(0));
const btn = screen.getByRole("button", { name: /export csv/i });
await user.click(btn);
await waitFor(() => expect(exportCalled).toBe(true));
});
// (d) selecting "tutti" drives sqlPreview with the large/unlimited limit
test("(d) selecting tutti re-fetches preview with the large limit", async () => {
const limits: unknown[] = [];
server.use(
http.post(`${BASE}/sessions/sess-4/sql/preview`, async ({ request }) => {
const body = (await request.json()) as { limit?: number };
limits.push(body.limit);
return HttpResponse.json({
columns: ["id"],
rows: [[1], [2]],
execution_ms: 3,
truncated: false,
limit: body.limit ?? 10,
offset: 0,
});
})
);
const user = userEvent.setup();
render(wrap(<ResultsPanel sessionId="sess-4" />));
// Initial fetch uses limit 10
await waitFor(() => expect(limits).toContain(10));
// Select "all"
const select = screen.getByLabelText(/rows to display/i);
await user.selectOptions(select, "all");
// A re-fetch with the large/unlimited limit must occur
await waitFor(() => expect(limits.some((l) => (l as number) > 10)).toBe(true));
});
-67
View File
@@ -1,67 +0,0 @@
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { sqlPreview, sqlExport } from "../api/sql";
import { PreviewGrid } from "./PreviewGrid";
interface Props {
sessionId: string;
}
export function ResultsPanel({ sessionId }: Props) {
const [limit, setLimit] = useState<number>(10);
const { data, isLoading, error } = useQuery({
queryKey: ["preview", sessionId, limit],
queryFn: () => sqlPreview(sessionId, { limit, offset: 0 }),
});
async function handleExport() {
await sqlExport(sessionId);
}
const isScalar =
data != null && data.columns.length === 1 && data.rows.length === 1;
return (
<div>
{/* Limit selector */}
<div className="mb-2 flex items-center gap-2">
<select
className="rounded-lg border border-input bg-card px-2 py-1 text-sm shadow-xs outline-none transition-[border-color,box-shadow] focus:border-primary/50 focus:ring-2 focus:ring-ring/20"
value={limit}
onChange={(e) =>
setLimit(e.target.value === "all" ? 1_000_000 : Number(e.target.value))
}
aria-label="Rows to display"
>
<option value={10}>10</option>
<option value="all">all</option>
</select>
<button
className="rounded-md border border-border/70 px-3 py-1 text-sm shadow-xs transition-colors hover:bg-accent"
onClick={handleExport}
>
Export CSV
</button>
</div>
{/* Loading / error states */}
{isLoading && <div>Loading…</div>}
{error && <div>Error: {String(error)}</div>}
{/* Scalar result: 1 col × 1 row → bold value */}
{data && isScalar && (
<strong data-testid="scalar-value">{String(data.rows[0][0])}</strong>
)}
{/* Grid for everything else */}
{data && !isScalar && (
<>
<PreviewGrid columns={data.columns} rows={data.rows} />
{data.truncated && <div>(results truncated)</div>}
</>
)}
</div>
);
}
+1 -2
View File
@@ -1,5 +1,4 @@
// TypeScript mirrors of the v2 artifact payload contracts (see
// .superpowers/sdd/review-gates-v2/contracts.md). Detection everywhere is
// TypeScript mirrors of the persisted v2 artifact payload contracts. Detection everywhere is
// `data.schema_version === 2` + a shape-guard; anything else falls back to the
// legacy renderers in ArtifactView.