diff --git a/client/src/components/__tests__/ResourcesTab.test.tsx b/client/src/components/__tests__/ResourcesTab.test.tsx
new file mode 100644
index 00000000..dd12cced
--- /dev/null
+++ b/client/src/components/__tests__/ResourcesTab.test.tsx
@@ -0,0 +1,285 @@
+import { render, screen, fireEvent } from "@testing-library/react";
+import "@testing-library/jest-dom";
+import { Tabs } from "@/components/ui/tabs";
+import ResourcesTab from "../ResourcesTab";
+import { ResourceTemplate, Resource } from "@modelcontextprotocol/sdk/types.js";
+
+// Mock the hooks and components
+jest.mock("@/lib/hooks/useCompletionState", () => ({
+ useCompletionState: () => ({
+ completions: {},
+ clearCompletions: jest.fn(),
+ requestCompletions: jest.fn(),
+ }),
+}));
+
+jest.mock("../JsonView", () => {
+ return function MockJsonView({ data }: { data: string }) {
+ return
{data}
;
+ };
+});
+
+jest.mock("@/components/ui/combobox", () => ({
+ Combobox: ({ id, value, onChange, placeholder }: {
+ id: string;
+ value: string;
+ onChange: (value: string) => void;
+ placeholder: string;
+ }) => (
+ onChange(e.target.value)}
+ placeholder={placeholder}
+ data-testid={`combobox-${id}`}
+ />
+ ),
+}));
+
+jest.mock("@/components/ui/label", () => ({
+ Label: ({ htmlFor, children }: {
+ htmlFor: string;
+ children: React.ReactNode;
+ }) => (
+
+ ),
+}));
+
+jest.mock("@/components/ui/button", () => ({
+ Button: ({ children, onClick, disabled, ...props }: {
+ children: React.ReactNode;
+ onClick?: () => void;
+ disabled?: boolean;
+ [key: string]: unknown;
+ }) => (
+
+ ),
+}));
+
+describe("ResourcesTab - Template Query Parameters", () => {
+ const mockListResources = jest.fn();
+ const mockClearResources = jest.fn();
+ const mockListResourceTemplates = jest.fn();
+ const mockClearResourceTemplates = jest.fn();
+ const mockReadResource = jest.fn();
+ const mockSetSelectedResource = jest.fn();
+ const mockHandleCompletion = jest.fn();
+ const mockSubscribeToResource = jest.fn();
+ const mockUnsubscribeFromResource = jest.fn();
+
+ const mockResourceTemplate: ResourceTemplate = {
+ name: "Users API",
+ uriTemplate: "test://users{?name,limit,offset}",
+ description: "Fetch users with optional filtering and pagination",
+ };
+
+ const mockResource: Resource = {
+ uri: "test://users?name=john&limit=10&offset=0",
+ name: "Users Resource",
+ description: "Expanded users resource",
+ };
+
+ const defaultProps = {
+ resources: [],
+ resourceTemplates: [mockResourceTemplate],
+ listResources: mockListResources,
+ clearResources: mockClearResources,
+ listResourceTemplates: mockListResourceTemplates,
+ clearResourceTemplates: mockClearResourceTemplates,
+ readResource: mockReadResource,
+ selectedResource: null,
+ setSelectedResource: mockSetSelectedResource,
+ handleCompletion: mockHandleCompletion,
+ completionsSupported: true,
+ resourceContent: "",
+ nextCursor: undefined,
+ nextTemplateCursor: undefined,
+ error: null,
+ resourceSubscriptionsSupported: false,
+ resourceSubscriptions: new Set(),
+ subscribeToResource: mockSubscribeToResource,
+ unsubscribeFromResource: mockUnsubscribeFromResource,
+ };
+
+ const renderResourcesTab = (props = {}) =>
+ render(
+
+
+ ,
+ );
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it("should parse and display template variables from URI template", () => {
+ renderResourcesTab();
+
+ // Click on the resource template to select it
+ fireEvent.click(screen.getByText("Users API"));
+
+ // Check that input fields are rendered for each template variable
+ expect(screen.getByTestId("combobox-name")).toBeInTheDocument();
+ expect(screen.getByTestId("combobox-limit")).toBeInTheDocument();
+ expect(screen.getByTestId("combobox-offset")).toBeInTheDocument();
+ });
+
+ it("should display template description when template is selected", () => {
+ renderResourcesTab();
+
+ // Click on the resource template to select it
+ fireEvent.click(screen.getByText("Users API"));
+
+ expect(screen.getByText("Fetch users with optional filtering and pagination")).toBeInTheDocument();
+ });
+
+ it("should handle template value changes", () => {
+ renderResourcesTab();
+
+ // Click on the resource template to select it
+ fireEvent.click(screen.getByText("Users API"));
+
+ // Find and fill template value inputs
+ const nameInput = screen.getByTestId("combobox-name");
+ const limitInput = screen.getByTestId("combobox-limit");
+ const offsetInput = screen.getByTestId("combobox-offset");
+
+ fireEvent.change(nameInput, { target: { value: "john" } });
+ fireEvent.change(limitInput, { target: { value: "10" } });
+ fireEvent.change(offsetInput, { target: { value: "0" } });
+
+ expect(nameInput).toHaveValue("john");
+ expect(limitInput).toHaveValue("10");
+ expect(offsetInput).toHaveValue("0");
+ });
+
+ it("should expand template and read resource when Read Resource button is clicked", async () => {
+ renderResourcesTab();
+
+ // Click on the resource template to select it
+ fireEvent.click(screen.getByText("Users API"));
+
+ // Fill template values
+ const nameInput = screen.getByTestId("combobox-name");
+ const limitInput = screen.getByTestId("combobox-limit");
+ const offsetInput = screen.getByTestId("combobox-offset");
+
+ fireEvent.change(nameInput, { target: { value: "john" } });
+ fireEvent.change(limitInput, { target: { value: "10" } });
+ fireEvent.change(offsetInput, { target: { value: "0" } });
+
+ // Click Read Resource button
+ const readResourceButton = screen.getByText("Read Resource");
+ expect(readResourceButton).not.toBeDisabled();
+
+ fireEvent.click(readResourceButton);
+
+ // Verify that readResource was called with the expanded URI
+ expect(mockReadResource).toHaveBeenCalledWith("test://users?name=john&limit=10&offset=0");
+
+ // Verify that setSelectedResource was called with the expanded resource
+ expect(mockSetSelectedResource).toHaveBeenCalledWith({
+ uri: "test://users?name=john&limit=10&offset=0",
+ name: "test://users?name=john&limit=10&offset=0"
+ });
+ });
+
+ it("should disable Read Resource button when no template values are provided", () => {
+ renderResourcesTab();
+
+ // Click on the resource template to select it
+ fireEvent.click(screen.getByText("Users API"));
+
+ // Read Resource button should be disabled when no values are provided
+ const readResourceButton = screen.getByText("Read Resource");
+ expect(readResourceButton).toBeDisabled();
+ });
+
+ it("should handle partial template values correctly", () => {
+ renderResourcesTab();
+
+ // Click on the resource template to select it
+ fireEvent.click(screen.getByText("Users API"));
+
+ // Fill only some template values
+ const nameInput = screen.getByTestId("combobox-name");
+ fireEvent.change(nameInput, { target: { value: "john" } });
+
+ // Read Resource button should be enabled with partial values
+ const readResourceButton = screen.getByText("Read Resource");
+ expect(readResourceButton).not.toBeDisabled();
+
+ fireEvent.click(readResourceButton);
+
+ // Should expand with only the provided values
+ expect(mockReadResource).toHaveBeenCalledWith("test://users?name=john");
+ });
+
+ it("should handle special characters in template values", () => {
+ renderResourcesTab();
+
+ // Click on the resource template to select it
+ fireEvent.click(screen.getByText("Users API"));
+
+ // Fill template values with special characters
+ const nameInput = screen.getByTestId("combobox-name");
+ fireEvent.change(nameInput, { target: { value: "john doe" } });
+
+ fireEvent.click(screen.getByText("Read Resource"));
+
+ // Should properly encode special characters
+ expect(mockReadResource).toHaveBeenCalledWith("test://users?name=john%20doe");
+ });
+
+ it("should clear template values when switching between templates", () => {
+ const anotherTemplate: ResourceTemplate = {
+ name: "Posts API",
+ uriTemplate: "test://posts{?author,category}",
+ description: "Fetch posts by author and category",
+ };
+
+ renderResourcesTab({
+ resourceTemplates: [mockResourceTemplate, anotherTemplate]
+ });
+
+ // Select first template and fill values
+ fireEvent.click(screen.getByText("Users API"));
+ const nameInput = screen.getByTestId("combobox-name");
+ fireEvent.change(nameInput, { target: { value: "john" } });
+
+ // Switch to second template
+ fireEvent.click(screen.getByText("Posts API"));
+
+ // Should show new template fields and clear previous values
+ expect(screen.getByTestId("combobox-author")).toBeInTheDocument();
+ expect(screen.getByTestId("combobox-category")).toBeInTheDocument();
+ expect(screen.queryByTestId("combobox-name")).not.toBeInTheDocument();
+ });
+
+ it("should display resource content when a resource is selected", () => {
+ const resourceContent = '{"users": [{"id": 1, "name": "John"}]}';
+
+ renderResourcesTab({
+ selectedResource: mockResource,
+ resourceContent: resourceContent
+ });
+
+ expect(screen.getByTestId("json-view")).toBeInTheDocument();
+ expect(screen.getByText(resourceContent)).toBeInTheDocument();
+ });
+
+ it("should show alert when no resource or template is selected", () => {
+ renderResourcesTab();
+
+ expect(screen.getByText("Select a resource or template from the list to view its contents")).toBeInTheDocument();
+ });
+});