Skip to content

Commit 79bea4b

Browse files
committed
chore: Artifact Visualization Tests
1 parent ec2666c commit 79bea4b

8 files changed

Lines changed: 1096 additions & 0 deletions

File tree

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

0 commit comments

Comments
 (0)