Skip to content

Commit a7053a0

Browse files
committed
Add unit tests for CodeSnippet and language utilities
Test stripSdkType, getLanguageInfo, and SDK_PREFIXES in languages.ts Test CodeSnippet rendering, SDK selector, and language resolution Update vite config to include test files Update stories with client/agent examples
1 parent 0df76d8 commit a7053a0

File tree

4 files changed

+461
-0
lines changed

4 files changed

+461
-0
lines changed

src/core/CodeSnippet/CodeSnippet.stories.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ npm run start`,
108108
* - API key selector shows when apiKeys are provided
109109
* - SDK type selector (Realtime/REST) appears when language attributes have
110110
* "realtime_" or "rest_" prefixes
111+
* - Client/Agent SDK types for page-level controlled code snippets (e.g. AI Transport)
111112
* - Special shell command mode for terminal commands
112113
* - Copy to clipboard functionality
113114
*/
Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
5+
import React from "react";
6+
import { describe, expect, it, vi, afterEach } from "vitest";
7+
import { render, screen, within, cleanup } from "@testing-library/react";
8+
import "@testing-library/jest-dom/vitest";
9+
import CodeSnippet from "../CodeSnippet";
10+
11+
afterEach(cleanup);
12+
13+
// Mock child components to avoid complex rendering dependencies
14+
vi.mock("../Code", () => ({
15+
default: ({ snippet, language }: { snippet: string; language: string }) => (
16+
<div data-testid="code-block" data-language={language}>
17+
{snippet}
18+
</div>
19+
),
20+
}));
21+
22+
vi.mock("../Icon", () => ({
23+
default: ({ name }: { name: string }) => (
24+
<span data-testid="icon" data-icon={name} />
25+
),
26+
}));
27+
28+
vi.mock("../SegmentedControl", () => ({
29+
default: ({
30+
children,
31+
onClick,
32+
active,
33+
}: {
34+
children: React.ReactNode;
35+
onClick: () => void;
36+
active: boolean;
37+
}) => (
38+
<button data-testid="sdk-toggle" data-active={active} onClick={onClick}>
39+
{children}
40+
</button>
41+
),
42+
}));
43+
44+
vi.mock("./LanguageSelector", () => ({
45+
default: ({
46+
languages,
47+
activeLanguage,
48+
onLanguageChange,
49+
}: {
50+
languages: string[];
51+
activeLanguage: string;
52+
onLanguageChange: (lang: string) => void;
53+
}) => (
54+
<div data-testid="language-selector" data-active={activeLanguage}>
55+
{languages.map((lang) => (
56+
<button
57+
key={lang}
58+
data-testid={`lang-${lang}`}
59+
onClick={() => onLanguageChange(lang)}
60+
>
61+
{lang}
62+
</button>
63+
))}
64+
</div>
65+
),
66+
}));
67+
68+
vi.mock("./CopyButton", () => ({
69+
default: () => <button data-testid="copy-button">Copy</button>,
70+
}));
71+
72+
vi.mock("./ApiKeySelector", () => ({
73+
default: () => <div data-testid="api-key-selector" />,
74+
}));
75+
76+
vi.mock("./PlainCodeView", () => ({
77+
default: ({ content, language }: { content: string; language: string }) => (
78+
<div data-testid="plain-code-view" data-language={language}>
79+
{content}
80+
</div>
81+
),
82+
}));
83+
84+
const makeSnippet = (lang: string, content: string) => (
85+
<pre>
86+
<code className={`language-${lang}`}>{content}</code>
87+
</pre>
88+
);
89+
90+
describe("CodeSnippet", () => {
91+
describe("basic rendering", () => {
92+
it("renders a single language snippet", () => {
93+
render(
94+
<CodeSnippet lang="javascript">
95+
{makeSnippet("javascript", "console.log('hello')")}
96+
</CodeSnippet>,
97+
);
98+
expect(screen.getByTestId("code-block").textContent).toBe(
99+
"console.log('hello')",
100+
);
101+
});
102+
103+
it("renders a language label for a single non-fixed language", () => {
104+
const { container } = render(
105+
<CodeSnippet lang="javascript">
106+
{makeSnippet("javascript", "const x = 1;")}
107+
</CodeSnippet>,
108+
);
109+
const labelSpans = container.querySelectorAll(".ui-text-label4");
110+
const labels = Array.from(labelSpans).map((el) => el.textContent);
111+
expect(labels).toContain("JavaScript");
112+
});
113+
114+
it("renders the language selector when multiple languages are available", () => {
115+
render(
116+
<CodeSnippet lang="javascript">
117+
{makeSnippet("javascript", "const x = 1;")}
118+
{makeSnippet("python", "x = 1")}
119+
</CodeSnippet>,
120+
);
121+
expect(screen.getByTestId("language-selector")).toBeInTheDocument();
122+
});
123+
});
124+
125+
describe("realtime/rest SDK types", () => {
126+
it("shows SDK selector for realtime/rest prefixed languages", () => {
127+
render(
128+
<CodeSnippet lang="javascript" sdk="realtime">
129+
{makeSnippet("realtime_javascript", "// realtime code")}
130+
{makeSnippet("rest_javascript", "// rest code")}
131+
</CodeSnippet>,
132+
);
133+
const toggles = screen.getAllByTestId("sdk-toggle");
134+
expect(toggles).toHaveLength(2);
135+
expect(toggles[0].textContent).toBe("Realtime");
136+
expect(toggles[1].textContent).toBe("REST");
137+
});
138+
139+
it("shows single SDK label when only one SDK type present", () => {
140+
render(
141+
<CodeSnippet lang="javascript" sdk="realtime">
142+
{makeSnippet("realtime_javascript", "// realtime only")}
143+
</CodeSnippet>,
144+
);
145+
const toggles = screen.getAllByTestId("sdk-toggle");
146+
expect(toggles[0].textContent).toBe("Realtime");
147+
});
148+
149+
it("resolves to the correct SDK when only one type is available", () => {
150+
render(
151+
<CodeSnippet lang="javascript" sdk="realtime">
152+
{makeSnippet("rest_javascript", "// rest only")}
153+
</CodeSnippet>,
154+
);
155+
// Should fall back to rest since that's the only type available
156+
expect(screen.getByTestId("code-block").textContent).toBe("// rest only");
157+
});
158+
});
159+
160+
describe("client/agent SDK types", () => {
161+
it("does not show SDK selector for client/agent prefixed languages", () => {
162+
render(
163+
<CodeSnippet lang="javascript" sdk="client" fixed>
164+
{makeSnippet("client_javascript", "// client code")}
165+
{makeSnippet("agent_python", "// agent code")}
166+
</CodeSnippet>,
167+
);
168+
expect(screen.queryAllByTestId("sdk-toggle")).toHaveLength(0);
169+
});
170+
171+
it("renders client_ prefixed snippet with fixed mode and sdk=client", () => {
172+
render(
173+
<CodeSnippet lang="javascript" sdk="client" fixed>
174+
{makeSnippet("client_javascript", "// client JS")}
175+
{makeSnippet("agent_python", "// agent python")}
176+
</CodeSnippet>,
177+
);
178+
expect(screen.getByTestId("code-block").textContent).toBe("// client JS");
179+
});
180+
181+
it("renders agent_ prefixed snippet with fixed mode and sdk=agent", () => {
182+
render(
183+
<CodeSnippet lang="python" sdk="agent" fixed>
184+
{makeSnippet("client_javascript", "// client JS")}
185+
{makeSnippet("agent_python", "// agent python")}
186+
</CodeSnippet>,
187+
);
188+
expect(screen.getByTestId("code-block").textContent).toBe(
189+
"// agent python",
190+
);
191+
});
192+
193+
it("falls back to first language with matching prefix when exact match not found", () => {
194+
render(
195+
<CodeSnippet lang="ruby" sdk="client" fixed>
196+
{makeSnippet("client_javascript", "// client JS")}
197+
{makeSnippet("client_typescript", "// client TS")}
198+
</CodeSnippet>,
199+
);
200+
// Ruby doesn't exist with client_ prefix, should fall back to first client_ language
201+
expect(screen.getByTestId("code-block").textContent).toBe("// client JS");
202+
});
203+
});
204+
205+
describe("fixed mode", () => {
206+
it("shows a read-only language label in fixed mode", () => {
207+
const { container } = render(
208+
<CodeSnippet lang="javascript" fixed>
209+
{makeSnippet("javascript", "const x = 1;")}
210+
</CodeSnippet>,
211+
);
212+
const labelSpans = container.querySelectorAll(".ui-text-label4");
213+
const labels = Array.from(labelSpans).map((el) => el.textContent);
214+
expect(labels).toContain("JavaScript");
215+
expect(screen.queryByTestId("language-selector")).not.toBeInTheDocument();
216+
});
217+
218+
it("shows correct label for client_ prefixed language in fixed mode", () => {
219+
const { container } = render(
220+
<CodeSnippet lang="python" sdk="client" fixed>
221+
{makeSnippet("client_python", "# client python")}
222+
</CodeSnippet>,
223+
);
224+
const labelSpans = container.querySelectorAll(".ui-text-label4");
225+
const labels = Array.from(labelSpans).map((el) => el.textContent);
226+
expect(labels).toContain("Python");
227+
});
228+
229+
it("hides language selector in fixed mode even with multiple languages", () => {
230+
render(
231+
<CodeSnippet lang="javascript" fixed>
232+
{makeSnippet("javascript", "const x = 1;")}
233+
{makeSnippet("python", "x = 1")}
234+
</CodeSnippet>,
235+
);
236+
expect(screen.queryByTestId("language-selector")).not.toBeInTheDocument();
237+
});
238+
});
239+
240+
describe("header row", () => {
241+
it("renders a header row when headerRow is true", () => {
242+
render(
243+
<CodeSnippet lang="javascript" headerRow title="My Code">
244+
{makeSnippet("javascript", "const x = 1;")}
245+
</CodeSnippet>,
246+
);
247+
expect(screen.getByText("My Code")).toBeInTheDocument();
248+
});
249+
});
250+
251+
describe("plain command mode", () => {
252+
it("renders shell commands in plain mode", () => {
253+
render(
254+
<CodeSnippet lang="shell">
255+
{makeSnippet("shell", "npm install")}
256+
</CodeSnippet>,
257+
);
258+
expect(screen.getByTestId("plain-code-view").textContent).toBe(
259+
"npm install",
260+
);
261+
});
262+
263+
it("renders text commands in plain mode", () => {
264+
render(
265+
<CodeSnippet lang="text">
266+
{makeSnippet("text", "Hello world")}
267+
</CodeSnippet>,
268+
);
269+
expect(screen.getByTestId("plain-code-view").textContent).toBe(
270+
"Hello world",
271+
);
272+
});
273+
});
274+
275+
describe("onChange callback", () => {
276+
it("passes stripped language and sdk type on language change", () => {
277+
const onChange = vi.fn();
278+
render(
279+
<CodeSnippet lang="javascript" sdk="realtime" onChange={onChange}>
280+
{makeSnippet("realtime_javascript", "// js code")}
281+
{makeSnippet("realtime_python", "# python code")}
282+
</CodeSnippet>,
283+
);
284+
const pyButton = screen.getByTestId("lang-realtime_python");
285+
pyButton.click();
286+
expect(onChange).toHaveBeenCalledWith("python", "realtime");
287+
});
288+
289+
it("passes undefined sdk when no SDK type", () => {
290+
const onChange = vi.fn();
291+
render(
292+
<CodeSnippet lang="javascript" onChange={onChange}>
293+
{makeSnippet("javascript", "// js")}
294+
{makeSnippet("python", "# py")}
295+
</CodeSnippet>,
296+
);
297+
const pyButton = screen.getByTestId("lang-python");
298+
pyButton.click();
299+
expect(onChange).toHaveBeenCalledWith("python", undefined);
300+
});
301+
});
302+
303+
describe("missing language snippet", () => {
304+
it("shows a message when active language has no snippet", () => {
305+
render(
306+
<CodeSnippet lang="ruby">
307+
{makeSnippet("javascript", "const x = 1;")}
308+
{makeSnippet("python", "x = 1")}
309+
</CodeSnippet>,
310+
);
311+
expect(
312+
screen.getByText(/currently viewing the Ruby docs/),
313+
).toBeInTheDocument();
314+
});
315+
});
316+
317+
describe("JSON-only snippets", () => {
318+
it("always shows JSON content regardless of selected language", () => {
319+
render(
320+
<CodeSnippet lang="ruby">
321+
{makeSnippet("json", '{"key": "value"}')}
322+
</CodeSnippet>,
323+
);
324+
expect(screen.getByTestId("code-block").textContent).toBe(
325+
'{"key": "value"}',
326+
);
327+
});
328+
});
329+
330+
describe("language ordering", () => {
331+
it("respects custom language ordering", () => {
332+
render(
333+
<CodeSnippet
334+
lang="javascript"
335+
languageOrdering={["python", "typescript", "javascript"]}
336+
>
337+
{makeSnippet("javascript", "// js")}
338+
{makeSnippet("typescript", "// ts")}
339+
{makeSnippet("python", "# py")}
340+
</CodeSnippet>,
341+
);
342+
const selector = screen.getByTestId("language-selector");
343+
const buttons = within(selector).getAllByRole("button");
344+
expect(buttons[0].textContent).toBe("python");
345+
expect(buttons[1].textContent).toBe("typescript");
346+
expect(buttons[2].textContent).toBe("javascript");
347+
});
348+
});
349+
});

0 commit comments

Comments
 (0)