Skip to content

Commit 172b694

Browse files
committed
chore: Artifact Visualization Tests
1 parent 586d256 commit 172b694

8 files changed

Lines changed: 1086 additions & 0 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
2+
import { render, screen, waitFor } from "@testing-library/react";
3+
import { userEvent } from "@testing-library/user-event";
4+
import { beforeEach, describe, expect, it, vi } from "vitest";
5+
6+
import type { ArtifactNodeResponse } from "@/api/types.gen";
7+
8+
import ArtifactVisualizer from "./ArtifactVisualizer";
9+
10+
vi.mock("@/providers/BackendProvider", () => ({
11+
useBackend: () => ({ backendUrl: "http://localhost:8000" }),
12+
}));
13+
14+
vi.mock("@/services/executionService", () => ({
15+
getArtifactSignedUrl: vi.fn().mockResolvedValue({
16+
signed_url: "https://storage.example.com/signed",
17+
}),
18+
}));
19+
20+
vi.mock("./TextVisualizer", () => ({
21+
default: ({ value, signedUrl }: { value?: string; signedUrl?: string }) => (
22+
<div
23+
data-testid="text-visualizer"
24+
data-value={value}
25+
data-signed-url={signedUrl}
26+
/>
27+
),
28+
}));
29+
30+
vi.mock("./ImageVisualizer", () => ({
31+
default: ({ src, name }: { src: string; name: string }) => (
32+
<div data-testid="image-visualizer" data-src={src} data-name={name} />
33+
),
34+
}));
35+
36+
vi.mock("./CsvVisualizer", () => ({
37+
default: ({ value, signedUrl }: { value?: string; signedUrl?: string }) => (
38+
<div
39+
data-testid="csv-visualizer"
40+
data-value={value}
41+
data-signed-url={signedUrl}
42+
/>
43+
),
44+
}));
45+
46+
vi.mock("./JsonVisualizer", () => ({
47+
default: ({
48+
value,
49+
signedUrl,
50+
name,
51+
}: {
52+
value?: string;
53+
signedUrl?: string;
54+
name: string;
55+
}) => (
56+
<div
57+
data-testid="json-visualizer"
58+
data-value={value}
59+
data-signed-url={signedUrl}
60+
data-name={name}
61+
/>
62+
),
63+
}));
64+
65+
vi.mock("./ParquetVisualizer", () => ({
66+
default: ({ signedUrl }: { signedUrl: string }) => (
67+
<div data-testid="parquet-visualizer" data-signed-url={signedUrl} />
68+
),
69+
}));
70+
71+
const queryClient = new QueryClient({
72+
defaultOptions: { queries: { retry: false } },
73+
});
74+
75+
const renderWithQuery = (ui: React.ReactElement) =>
76+
render(<QueryClientProvider client={queryClient}>{ui}</QueryClientProvider>);
77+
78+
const makeArtifact = (
79+
overrides?: Partial<ArtifactNodeResponse>,
80+
): ArtifactNodeResponse => ({
81+
id: "artifact-1",
82+
artifact_data: { total_size: 1024, is_dir: false },
83+
...overrides,
84+
});
85+
86+
beforeEach(() => {
87+
queryClient.clear();
88+
});
89+
90+
describe("ArtifactVisualizer", () => {
91+
describe("trigger button", () => {
92+
it("renders Eye + Preview button when no value is provided", () => {
93+
renderWithQuery(
94+
<ArtifactVisualizer
95+
artifact={makeArtifact()}
96+
name="output"
97+
type="text"
98+
/>,
99+
);
100+
101+
expect(screen.getByText("Preview")).toBeInTheDocument();
102+
});
103+
104+
it("renders Maximize2 button when value is provided", () => {
105+
renderWithQuery(
106+
<ArtifactVisualizer
107+
artifact={makeArtifact()}
108+
name="output"
109+
type="text"
110+
value="inline content"
111+
/>,
112+
);
113+
114+
expect(screen.queryByText("Preview")).not.toBeInTheDocument();
115+
// The maximize button should be present (ghost button without text)
116+
expect(screen.getByRole("button")).toBeInTheDocument();
117+
});
118+
});
119+
120+
it("returns null for non-visualizable types", () => {
121+
const { container } = renderWithQuery(
122+
<ArtifactVisualizer
123+
artifact={makeArtifact()}
124+
name="output"
125+
type="Unknown"
126+
/>,
127+
);
128+
129+
expect(container.innerHTML).toBe("");
130+
});
131+
132+
describe("inline value rendering", () => {
133+
it("renders TextVisualizer with value for text type", async () => {
134+
renderWithQuery(
135+
<ArtifactVisualizer
136+
artifact={makeArtifact()}
137+
name="output"
138+
type="text"
139+
value="hello"
140+
/>,
141+
);
142+
143+
await userEvent.click(screen.getByRole("button"));
144+
145+
await waitFor(() => {
146+
const viz = screen.getByTestId("text-visualizer");
147+
expect(viz).toHaveAttribute("data-value", "hello");
148+
});
149+
});
150+
151+
it("renders CsvVisualizer with value for csv type", async () => {
152+
renderWithQuery(
153+
<ArtifactVisualizer
154+
artifact={makeArtifact()}
155+
name="data"
156+
type="CSV"
157+
value={"a,b\n1,2"}
158+
/>,
159+
);
160+
161+
await userEvent.click(screen.getByRole("button"));
162+
163+
await waitFor(() => {
164+
const viz = screen.getByTestId("csv-visualizer");
165+
expect(viz).toHaveAttribute("data-value", "a,b\n1,2");
166+
});
167+
});
168+
169+
it("renders CsvVisualizer with value for tsv type", async () => {
170+
renderWithQuery(
171+
<ArtifactVisualizer
172+
artifact={makeArtifact()}
173+
name="data"
174+
type="TSV"
175+
value={"a\tb\n1\t2"}
176+
/>,
177+
);
178+
179+
await userEvent.click(screen.getByRole("button"));
180+
181+
await waitFor(() => {
182+
const viz = screen.getByTestId("csv-visualizer");
183+
expect(viz).toHaveAttribute("data-value", "a\tb\n1\t2");
184+
});
185+
});
186+
187+
it("renders JsonVisualizer with value for jsonobject type", async () => {
188+
renderWithQuery(
189+
<ArtifactVisualizer
190+
artifact={makeArtifact()}
191+
name="config"
192+
type="JsonObject"
193+
value='{"key":"val"}'
194+
/>,
195+
);
196+
197+
await userEvent.click(screen.getByRole("button"));
198+
199+
await waitFor(() => {
200+
const viz = screen.getByTestId("json-visualizer");
201+
expect(viz).toHaveAttribute("data-value", '{"key":"val"}');
202+
});
203+
});
204+
});
205+
206+
describe("signed URL rendering", () => {
207+
it("renders TextVisualizer with signedUrl for text type", async () => {
208+
renderWithQuery(
209+
<ArtifactVisualizer artifact={makeArtifact()} name="log" type="text" />,
210+
);
211+
212+
await userEvent.click(screen.getByText("Preview"));
213+
214+
await waitFor(() => {
215+
const viz = screen.getByTestId("text-visualizer");
216+
expect(viz).toHaveAttribute(
217+
"data-signed-url",
218+
"https://storage.example.com/signed",
219+
);
220+
});
221+
});
222+
223+
it("renders ImageVisualizer for image type", async () => {
224+
renderWithQuery(
225+
<ArtifactVisualizer
226+
artifact={makeArtifact()}
227+
name="photo"
228+
type="image"
229+
/>,
230+
);
231+
232+
await userEvent.click(screen.getByText("Preview"));
233+
234+
await waitFor(() => {
235+
expect(screen.getByTestId("image-visualizer")).toBeInTheDocument();
236+
});
237+
});
238+
239+
it("renders ParquetVisualizer for apacheparquet type", async () => {
240+
renderWithQuery(
241+
<ArtifactVisualizer
242+
artifact={makeArtifact()}
243+
name="data"
244+
type="Apache Parquet"
245+
/>,
246+
);
247+
248+
await userEvent.click(screen.getByText("Preview"));
249+
250+
await waitFor(() => {
251+
expect(screen.getByTestId("parquet-visualizer")).toBeInTheDocument();
252+
});
253+
});
254+
});
255+
256+
describe("dialog header", () => {
257+
it("shows artifact name and type in dialog", async () => {
258+
renderWithQuery(
259+
<ArtifactVisualizer
260+
artifact={makeArtifact()}
261+
name="my-output"
262+
type="CSV"
263+
value="a,b"
264+
/>,
265+
);
266+
267+
await userEvent.click(screen.getByRole("button"));
268+
269+
await waitFor(() => {
270+
expect(screen.getByText("my-output")).toBeInTheDocument();
271+
expect(screen.getByText("CSV")).toBeInTheDocument();
272+
});
273+
});
274+
275+
it("shows artifact URI when available", async () => {
276+
renderWithQuery(
277+
<ArtifactVisualizer
278+
artifact={makeArtifact({
279+
artifact_data: {
280+
total_size: 100,
281+
is_dir: false,
282+
uri: "gs://bucket/path",
283+
},
284+
})}
285+
name="output"
286+
type="text"
287+
value="content"
288+
/>,
289+
);
290+
291+
await userEvent.click(screen.getByRole("button"));
292+
293+
await waitFor(() => {
294+
expect(screen.getByText("Copy URI")).toBeInTheDocument();
295+
});
296+
});
297+
});
298+
});

0 commit comments

Comments
 (0)