adding fixes as buttons were not visible for streamable-http transport type, as per PR review comment

This commit is contained in:
sumeetpardeshi
2025-05-12 20:17:28 -07:00
parent 1067f4d22f
commit 5b54ce1281
2 changed files with 156 additions and 95 deletions

View File

@@ -119,13 +119,22 @@ const Sidebar = ({
args: args.trim() ? args.split(/\s+/) : [], args: args.trim() ? args.split(/\s+/) : [],
env: { ...env }, env: { ...env },
}; };
} else { }
if (transportType === "sse") {
return { return {
type: "sse", type: "sse",
url: sseUrl, url: sseUrl,
note: "For SSE connections, add this URL directly in Client", note: "For SSE connections, add this URL directly in Client",
}; };
} }
if (transportType === "streamable-http") {
return {
type: "streamable-http",
url: sseUrl,
note: "For Streamable HTTP connections, add this URL directly in Client",
};
}
return {};
}, [transportType, command, args, env, sseUrl]); }, [transportType, command, args, env, sseUrl]);
// Memoized config entry generator // Memoized config entry generator
@@ -266,44 +275,6 @@ const Sidebar = ({
className="font-mono" className="font-mono"
/> />
</div> </div>
<div className="grid grid-cols-2 gap-2 mt-2">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="sm"
onClick={handleCopyServerEntry}
className="w-full"
>
{copiedServerEntry ? (
<CheckCheck className="h-4 w-4 mr-2" />
) : (
<Copy className="h-4 w-4 mr-2" />
)}
Server Entry
</Button>
</TooltipTrigger>
<TooltipContent>Copy Server Entry</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="sm"
onClick={handleCopyServerFile}
className="w-full"
>
{copiedServerFile ? (
<CheckCheck className="h-4 w-4 mr-2" />
) : (
<Copy className="h-4 w-4 mr-2" />
)}
Servers File
</Button>
</TooltipTrigger>
<TooltipContent>Copy Servers File</TooltipContent>
</Tooltip>
</div>
</> </>
) : ( ) : (
<> <>
@@ -319,22 +290,6 @@ const Sidebar = ({
className="font-mono" className="font-mono"
/> />
</div> </div>
<div className="w-full mt-2">
<Button
variant="outline"
size="sm"
onClick={handleCopyServerFile}
className="w-full"
title="Copy SSE URL Configuration"
>
{copiedServerFile ? (
<CheckCheck className="h-4 w-4 mr-2" />
) : (
<Copy className="h-4 w-4 mr-2" />
)}
Copy Servers File
</Button>
</div>
<div className="space-y-2"> <div className="space-y-2">
<Button <Button
variant="outline" variant="outline"
@@ -382,6 +337,7 @@ const Sidebar = ({
</div> </div>
</> </>
)} )}
{transportType === "stdio" && ( {transportType === "stdio" && (
<div className="space-y-2"> <div className="space-y-2">
<Button <Button
@@ -507,6 +463,46 @@ const Sidebar = ({
</div> </div>
)} )}
{/* Always show both copy buttons for all transport types */}
<div className="grid grid-cols-2 gap-2 mt-2">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="sm"
onClick={handleCopyServerEntry}
className="w-full"
>
{copiedServerEntry ? (
<CheckCheck className="h-4 w-4 mr-2" />
) : (
<Copy className="h-4 w-4 mr-2" />
)}
Server Entry
</Button>
</TooltipTrigger>
<TooltipContent>Copy Server Entry</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="sm"
onClick={handleCopyServerFile}
className="w-full"
>
{copiedServerFile ? (
<CheckCheck className="h-4 w-4 mr-2" />
) : (
<Copy className="h-4 w-4 mr-2" />
)}
Servers File
</Button>
</TooltipTrigger>
<TooltipContent>Copy Servers File</TooltipContent>
</Tooltip>
</div>
{/* Configuration */} {/* Configuration */}
<div className="space-y-2"> <div className="space-y-2">
<Button <Button

View File

@@ -649,6 +649,31 @@ describe("Sidebar Environment Variables", () => {
jest.clearAllTimers(); jest.clearAllTimers();
}); });
const getCopyButtons = () => {
return {
serverEntry: screen.getByRole("button", { name: /server entry/i }),
serversFile: screen.getByRole("button", { name: /servers file/i }),
};
};
it("should render both copy buttons for all transport types", () => {
["stdio", "sse", "streamable-http"].forEach((transportType) => {
renderSidebar({ transportType });
// There should be exactly one Server Entry and one Servers File button per render
const serverEntryButtons = screen.getAllByRole("button", {
name: /server entry/i,
});
const serversFileButtons = screen.getAllByRole("button", {
name: /servers file/i,
});
expect(serverEntryButtons).toHaveLength(1);
expect(serversFileButtons).toHaveLength(1);
// Clean up DOM for next iteration
// (Testing Library's render does not auto-unmount in a loop)
document.body.innerHTML = "";
});
});
it("should copy server entry configuration to clipboard for STDIO transport", async () => { it("should copy server entry configuration to clipboard for STDIO transport", async () => {
const command = "node"; const command = "node";
const args = "--inspect server.js"; const args = "--inspect server.js";
@@ -661,20 +686,13 @@ describe("Sidebar Environment Variables", () => {
env, env,
}); });
// Use act to properly wrap the clipboard operations
await act(async () => { await act(async () => {
const copyServerEntryButton = screen.getByRole("button", { const { serverEntry } = getCopyButtons();
name: /server entry/i, fireEvent.click(serverEntry);
});
fireEvent.click(copyServerEntryButton);
// Fast-forward timers to handle the setTimeout
jest.runAllTimers(); jest.runAllTimers();
}); });
// Check clipboard API was called with the correct configuration
expect(mockClipboardWrite).toHaveBeenCalledTimes(1); expect(mockClipboardWrite).toHaveBeenCalledTimes(1);
const expectedConfig = JSON.stringify( const expectedConfig = JSON.stringify(
{ {
command, command,
@@ -684,7 +702,6 @@ describe("Sidebar Environment Variables", () => {
null, null,
2, 2,
); );
expect(mockClipboardWrite).toHaveBeenCalledWith(expectedConfig); expect(mockClipboardWrite).toHaveBeenCalledWith(expectedConfig);
}); });
@@ -701,18 +718,12 @@ describe("Sidebar Environment Variables", () => {
}); });
await act(async () => { await act(async () => {
const copyServersFileButton = screen.getByRole("button", { const { serversFile } = getCopyButtons();
name: /servers file/i, fireEvent.click(serversFile);
});
fireEvent.click(copyServersFileButton);
// Fast-forward timers to handle the setTimeout
jest.runAllTimers(); jest.runAllTimers();
}); });
// Check clipboard API was called with the correct configuration
expect(mockClipboardWrite).toHaveBeenCalledTimes(1); expect(mockClipboardWrite).toHaveBeenCalledTimes(1);
const expectedConfig = JSON.stringify( const expectedConfig = JSON.stringify(
{ {
mcpServers: { mcpServers: {
@@ -726,31 +737,43 @@ describe("Sidebar Environment Variables", () => {
null, null,
2, 2,
); );
expect(mockClipboardWrite).toHaveBeenCalledWith(expectedConfig);
});
it("should copy server entry configuration to clipboard for SSE transport", async () => {
const sseUrl = "http://localhost:3000/events";
renderSidebar({ transportType: "sse", sseUrl });
await act(async () => {
const { serverEntry } = getCopyButtons();
fireEvent.click(serverEntry);
jest.runAllTimers();
});
expect(mockClipboardWrite).toHaveBeenCalledTimes(1);
const expectedConfig = JSON.stringify(
{
type: "sse",
url: sseUrl,
note: "For SSE connections, add this URL directly in Client",
},
null,
2,
);
expect(mockClipboardWrite).toHaveBeenCalledWith(expectedConfig); expect(mockClipboardWrite).toHaveBeenCalledWith(expectedConfig);
}); });
it("should copy servers file configuration to clipboard for SSE transport", async () => { it("should copy servers file configuration to clipboard for SSE transport", async () => {
const sseUrl = "http://localhost:3000/events"; const sseUrl = "http://localhost:3000/events";
renderSidebar({ transportType: "sse", sseUrl });
renderSidebar({
transportType: "sse",
sseUrl,
});
await act(async () => { await act(async () => {
const copyServersFileButton = screen.getByRole("button", { const { serversFile } = getCopyButtons();
name: /servers file/i, fireEvent.click(serversFile);
});
fireEvent.click(copyServersFileButton);
// Fast-forward timers to handle the setTimeout
jest.runAllTimers(); jest.runAllTimers();
}); });
// Check clipboard API was called with the correct configuration
expect(mockClipboardWrite).toHaveBeenCalledTimes(1); expect(mockClipboardWrite).toHaveBeenCalledTimes(1);
const expectedConfig = JSON.stringify( const expectedConfig = JSON.stringify(
{ {
mcpServers: { mcpServers: {
@@ -764,7 +787,56 @@ describe("Sidebar Environment Variables", () => {
null, null,
2, 2,
); );
expect(mockClipboardWrite).toHaveBeenCalledWith(expectedConfig);
});
it("should copy server entry configuration to clipboard for streamable-http transport", async () => {
const sseUrl = "http://localhost:3001/sse";
renderSidebar({ transportType: "streamable-http", sseUrl });
await act(async () => {
const { serverEntry } = getCopyButtons();
fireEvent.click(serverEntry);
jest.runAllTimers();
});
expect(mockClipboardWrite).toHaveBeenCalledTimes(1);
const expectedConfig = JSON.stringify(
{
type: "streamable-http",
url: sseUrl,
note: "For Streamable HTTP connections, add this URL directly in Client",
},
null,
2,
);
expect(mockClipboardWrite).toHaveBeenCalledWith(expectedConfig);
});
it("should copy servers file configuration to clipboard for streamable-http transport", async () => {
const sseUrl = "http://localhost:3001/sse";
renderSidebar({ transportType: "streamable-http", sseUrl });
await act(async () => {
const { serversFile } = getCopyButtons();
fireEvent.click(serversFile);
jest.runAllTimers();
});
expect(mockClipboardWrite).toHaveBeenCalledTimes(1);
const expectedConfig = JSON.stringify(
{
mcpServers: {
"default-server": {
type: "streamable-http",
url: sseUrl,
note: "For Streamable HTTP connections, add this URL directly in Client",
},
},
},
null,
2,
);
expect(mockClipboardWrite).toHaveBeenCalledWith(expectedConfig); expect(mockClipboardWrite).toHaveBeenCalledWith(expectedConfig);
}); });
@@ -779,18 +851,12 @@ describe("Sidebar Environment Variables", () => {
}); });
await act(async () => { await act(async () => {
const copyServerEntryButton = screen.getByRole("button", { const { serverEntry } = getCopyButtons();
name: /server entry/i, fireEvent.click(serverEntry);
});
fireEvent.click(copyServerEntryButton);
// Fast-forward timers to handle the setTimeout
jest.runAllTimers(); jest.runAllTimers();
}); });
// Check clipboard API was called with empty args array
expect(mockClipboardWrite).toHaveBeenCalledTimes(1); expect(mockClipboardWrite).toHaveBeenCalledTimes(1);
const expectedConfig = JSON.stringify( const expectedConfig = JSON.stringify(
{ {
command, command,
@@ -800,7 +866,6 @@ describe("Sidebar Environment Variables", () => {
null, null,
2, 2,
); );
expect(mockClipboardWrite).toHaveBeenCalledWith(expectedConfig); expect(mockClipboardWrite).toHaveBeenCalledWith(expectedConfig);
}); });
}); });