Skip to content

Commit 21b86d4

Browse files
committed
add test
1 parent 26f7448 commit 21b86d4

File tree

1 file changed

+285
-0
lines changed

1 file changed

+285
-0
lines changed
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
import { render, screen, fireEvent } from "@testing-library/react";
2+
import "@testing-library/jest-dom";
3+
import { Tabs } from "@/components/ui/tabs";
4+
import ResourcesTab from "../ResourcesTab";
5+
import { ResourceTemplate, Resource } from "@modelcontextprotocol/sdk/types.js";
6+
7+
// Mock the hooks and components
8+
jest.mock("@/lib/hooks/useCompletionState", () => ({
9+
useCompletionState: () => ({
10+
completions: {},
11+
clearCompletions: jest.fn(),
12+
requestCompletions: jest.fn(),
13+
}),
14+
}));
15+
16+
jest.mock("../JsonView", () => {
17+
return function MockJsonView({ data }: { data: string }) {
18+
return <div data-testid="json-view">{data}</div>;
19+
};
20+
});
21+
22+
jest.mock("@/components/ui/combobox", () => ({
23+
Combobox: ({ id, value, onChange, placeholder }: {
24+
id: string;
25+
value: string;
26+
onChange: (value: string) => void;
27+
placeholder: string;
28+
}) => (
29+
<input
30+
id={id}
31+
value={value || ""}
32+
onChange={(e) => onChange(e.target.value)}
33+
placeholder={placeholder}
34+
data-testid={`combobox-${id}`}
35+
/>
36+
),
37+
}));
38+
39+
jest.mock("@/components/ui/label", () => ({
40+
Label: ({ htmlFor, children }: {
41+
htmlFor: string;
42+
children: React.ReactNode;
43+
}) => (
44+
<label htmlFor={htmlFor} data-testid={`label-${htmlFor}`}>
45+
{children}
46+
</label>
47+
),
48+
}));
49+
50+
jest.mock("@/components/ui/button", () => ({
51+
Button: ({ children, onClick, disabled, ...props }: {
52+
children: React.ReactNode;
53+
onClick?: () => void;
54+
disabled?: boolean;
55+
[key: string]: unknown;
56+
}) => (
57+
<button
58+
onClick={onClick}
59+
disabled={disabled}
60+
data-testid="button"
61+
{...props}
62+
>
63+
{children}
64+
</button>
65+
),
66+
}));
67+
68+
describe("ResourcesTab - Template Query Parameters", () => {
69+
const mockListResources = jest.fn();
70+
const mockClearResources = jest.fn();
71+
const mockListResourceTemplates = jest.fn();
72+
const mockClearResourceTemplates = jest.fn();
73+
const mockReadResource = jest.fn();
74+
const mockSetSelectedResource = jest.fn();
75+
const mockHandleCompletion = jest.fn();
76+
const mockSubscribeToResource = jest.fn();
77+
const mockUnsubscribeFromResource = jest.fn();
78+
79+
const mockResourceTemplate: ResourceTemplate = {
80+
name: "Users API",
81+
uriTemplate: "test://users{?name,limit,offset}",
82+
description: "Fetch users with optional filtering and pagination",
83+
};
84+
85+
const mockResource: Resource = {
86+
uri: "test://users?name=john&limit=10&offset=0",
87+
name: "Users Resource",
88+
description: "Expanded users resource",
89+
};
90+
91+
const defaultProps = {
92+
resources: [],
93+
resourceTemplates: [mockResourceTemplate],
94+
listResources: mockListResources,
95+
clearResources: mockClearResources,
96+
listResourceTemplates: mockListResourceTemplates,
97+
clearResourceTemplates: mockClearResourceTemplates,
98+
readResource: mockReadResource,
99+
selectedResource: null,
100+
setSelectedResource: mockSetSelectedResource,
101+
handleCompletion: mockHandleCompletion,
102+
completionsSupported: true,
103+
resourceContent: "",
104+
nextCursor: undefined,
105+
nextTemplateCursor: undefined,
106+
error: null,
107+
resourceSubscriptionsSupported: false,
108+
resourceSubscriptions: new Set<string>(),
109+
subscribeToResource: mockSubscribeToResource,
110+
unsubscribeFromResource: mockUnsubscribeFromResource,
111+
};
112+
113+
const renderResourcesTab = (props = {}) =>
114+
render(
115+
<Tabs defaultValue="resources">
116+
<ResourcesTab {...defaultProps} {...props} />
117+
</Tabs>,
118+
);
119+
120+
beforeEach(() => {
121+
jest.clearAllMocks();
122+
});
123+
124+
it("should parse and display template variables from URI template", () => {
125+
renderResourcesTab();
126+
127+
// Click on the resource template to select it
128+
fireEvent.click(screen.getByText("Users API"));
129+
130+
// Check that input fields are rendered for each template variable
131+
expect(screen.getByTestId("combobox-name")).toBeInTheDocument();
132+
expect(screen.getByTestId("combobox-limit")).toBeInTheDocument();
133+
expect(screen.getByTestId("combobox-offset")).toBeInTheDocument();
134+
});
135+
136+
it("should display template description when template is selected", () => {
137+
renderResourcesTab();
138+
139+
// Click on the resource template to select it
140+
fireEvent.click(screen.getByText("Users API"));
141+
142+
expect(screen.getByText("Fetch users with optional filtering and pagination")).toBeInTheDocument();
143+
});
144+
145+
it("should handle template value changes", () => {
146+
renderResourcesTab();
147+
148+
// Click on the resource template to select it
149+
fireEvent.click(screen.getByText("Users API"));
150+
151+
// Find and fill template value inputs
152+
const nameInput = screen.getByTestId("combobox-name");
153+
const limitInput = screen.getByTestId("combobox-limit");
154+
const offsetInput = screen.getByTestId("combobox-offset");
155+
156+
fireEvent.change(nameInput, { target: { value: "john" } });
157+
fireEvent.change(limitInput, { target: { value: "10" } });
158+
fireEvent.change(offsetInput, { target: { value: "0" } });
159+
160+
expect(nameInput).toHaveValue("john");
161+
expect(limitInput).toHaveValue("10");
162+
expect(offsetInput).toHaveValue("0");
163+
});
164+
165+
it("should expand template and read resource when Read Resource button is clicked", async () => {
166+
renderResourcesTab();
167+
168+
// Click on the resource template to select it
169+
fireEvent.click(screen.getByText("Users API"));
170+
171+
// Fill template values
172+
const nameInput = screen.getByTestId("combobox-name");
173+
const limitInput = screen.getByTestId("combobox-limit");
174+
const offsetInput = screen.getByTestId("combobox-offset");
175+
176+
fireEvent.change(nameInput, { target: { value: "john" } });
177+
fireEvent.change(limitInput, { target: { value: "10" } });
178+
fireEvent.change(offsetInput, { target: { value: "0" } });
179+
180+
// Click Read Resource button
181+
const readResourceButton = screen.getByText("Read Resource");
182+
expect(readResourceButton).not.toBeDisabled();
183+
184+
fireEvent.click(readResourceButton);
185+
186+
// Verify that readResource was called with the expanded URI
187+
expect(mockReadResource).toHaveBeenCalledWith("test://users?name=john&limit=10&offset=0");
188+
189+
// Verify that setSelectedResource was called with the expanded resource
190+
expect(mockSetSelectedResource).toHaveBeenCalledWith({
191+
uri: "test://users?name=john&limit=10&offset=0",
192+
name: "test://users?name=john&limit=10&offset=0"
193+
});
194+
});
195+
196+
it("should disable Read Resource button when no template values are provided", () => {
197+
renderResourcesTab();
198+
199+
// Click on the resource template to select it
200+
fireEvent.click(screen.getByText("Users API"));
201+
202+
// Read Resource button should be disabled when no values are provided
203+
const readResourceButton = screen.getByText("Read Resource");
204+
expect(readResourceButton).toBeDisabled();
205+
});
206+
207+
it("should handle partial template values correctly", () => {
208+
renderResourcesTab();
209+
210+
// Click on the resource template to select it
211+
fireEvent.click(screen.getByText("Users API"));
212+
213+
// Fill only some template values
214+
const nameInput = screen.getByTestId("combobox-name");
215+
fireEvent.change(nameInput, { target: { value: "john" } });
216+
217+
// Read Resource button should be enabled with partial values
218+
const readResourceButton = screen.getByText("Read Resource");
219+
expect(readResourceButton).not.toBeDisabled();
220+
221+
fireEvent.click(readResourceButton);
222+
223+
// Should expand with only the provided values
224+
expect(mockReadResource).toHaveBeenCalledWith("test://users?name=john");
225+
});
226+
227+
it("should handle special characters in template values", () => {
228+
renderResourcesTab();
229+
230+
// Click on the resource template to select it
231+
fireEvent.click(screen.getByText("Users API"));
232+
233+
// Fill template values with special characters
234+
const nameInput = screen.getByTestId("combobox-name");
235+
fireEvent.change(nameInput, { target: { value: "john doe" } });
236+
237+
fireEvent.click(screen.getByText("Read Resource"));
238+
239+
// Should properly encode special characters
240+
expect(mockReadResource).toHaveBeenCalledWith("test://users?name=john%20doe");
241+
});
242+
243+
it("should clear template values when switching between templates", () => {
244+
const anotherTemplate: ResourceTemplate = {
245+
name: "Posts API",
246+
uriTemplate: "test://posts{?author,category}",
247+
description: "Fetch posts by author and category",
248+
};
249+
250+
renderResourcesTab({
251+
resourceTemplates: [mockResourceTemplate, anotherTemplate]
252+
});
253+
254+
// Select first template and fill values
255+
fireEvent.click(screen.getByText("Users API"));
256+
const nameInput = screen.getByTestId("combobox-name");
257+
fireEvent.change(nameInput, { target: { value: "john" } });
258+
259+
// Switch to second template
260+
fireEvent.click(screen.getByText("Posts API"));
261+
262+
// Should show new template fields and clear previous values
263+
expect(screen.getByTestId("combobox-author")).toBeInTheDocument();
264+
expect(screen.getByTestId("combobox-category")).toBeInTheDocument();
265+
expect(screen.queryByTestId("combobox-name")).not.toBeInTheDocument();
266+
});
267+
268+
it("should display resource content when a resource is selected", () => {
269+
const resourceContent = '{"users": [{"id": 1, "name": "John"}]}';
270+
271+
renderResourcesTab({
272+
selectedResource: mockResource,
273+
resourceContent: resourceContent
274+
});
275+
276+
expect(screen.getByTestId("json-view")).toBeInTheDocument();
277+
expect(screen.getByText(resourceContent)).toBeInTheDocument();
278+
});
279+
280+
it("should show alert when no resource or template is selected", () => {
281+
renderResourcesTab();
282+
283+
expect(screen.getByText("Select a resource or template from the list to view its contents")).toBeInTheDocument();
284+
});
285+
});

0 commit comments

Comments
 (0)