Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/components/MarketplaceComments.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { cleanup, render, screen } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { DirectoryComments } from "@/components/directory/DirectoryComments";
import { McpComments } from "@/components/mcp/McpComments";
import { PromptComments } from "@/components/prompts/PromptComments";
import { SkillComments } from "@/components/skills/SkillComments";

const surfaces = [
{
name: "directory",
render: () => <DirectoryComments listingId="listing-1" isAuthenticated={false} />,
},
{
name: "MCP",
render: () => <McpComments slug="listing-1" isAuthenticated={false} />,
},
{
name: "prompt",
render: () => <PromptComments slug="listing-1" isAuthenticated={false} />,
},
{
name: "skill",
render: () => <SkillComments slug="listing-1" isAuthenticated={false} />,
},
];

describe("marketplace comment loading errors", () => {
beforeEach(() => {
vi.stubGlobal("fetch", vi.fn());
});

afterEach(() => {
cleanup();
vi.unstubAllGlobals();
});

it.each(surfaces)("does not report an empty $name thread when loading fails", async ({ render: renderSurface }) => {
vi.mocked(fetch).mockResolvedValue(
new Response(JSON.stringify({ error: "Comments unavailable" }), { status: 503 })
);

render(renderSurface());

expect(await screen.findByRole("alert")).toHaveTextContent("Comments unavailable");
expect(
screen.queryByText("No comments yet. Be the first to share your thoughts!")
).not.toBeInTheDocument();
});
});
21 changes: 13 additions & 8 deletions src/components/directory/DirectoryComments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,20 @@ export function DirectoryComments({ listingId, isAuthenticated }: DirectoryComme
async function fetchComments() {
try {
const res = await fetch(`/api/directory/${listingId}/comments`);
if (res.ok) {
const data = await res.json();
setComments(data.comments || []);
setTotal(data.total || 0);
if (!res.ok) {
const data = await res.json().catch(() => ({}));
setError(data.error || "Failed to load comments");
return;
}

const data = await res.json();
setComments(data.comments || []);
setTotal(data.total || 0);
} catch {
// silently fail
setError("Failed to load comments");
} finally {
setLoading(false);
}
setLoading(false);
}

async function handlePost(parentId: string | null = null) {
Expand Down Expand Up @@ -213,7 +218,7 @@ export function DirectoryComments({ listingId, isAuthenticated }: DirectoryComme
)}

{error && (
<div className="mb-4 p-3 bg-red-500/10 border border-red-500/20 rounded-lg text-red-500 text-sm">
<div role="alert" className="mb-4 p-3 bg-red-500/10 border border-red-500/20 rounded-lg text-red-500 text-sm">
{error}
</div>
)}
Expand All @@ -222,7 +227,7 @@ export function DirectoryComments({ listingId, isAuthenticated }: DirectoryComme
<div className="flex justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
) : comments.length > 0 ? (
) : error ? null : comments.length > 0 ? (
<div className="divide-y divide-border">
{comments.map((comment) => renderComment(comment))}
</div>
Expand Down
21 changes: 13 additions & 8 deletions src/components/mcp/McpComments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,20 @@ export function McpComments({ slug, isAuthenticated }: McpCommentsProps) {
async function fetchComments() {
try {
const res = await fetch(`/api/mcp/${slug}/comments`);
if (res.ok) {
const data = await res.json();
setComments(data.comments || []);
setTotal(data.total || 0);
if (!res.ok) {
const data = await res.json().catch(() => ({}));
setError(data.error || "Failed to load comments");
return;
}

const data = await res.json();
setComments(data.comments || []);
setTotal(data.total || 0);
} catch {
// silently fail
setError("Failed to load comments");
} finally {
setLoading(false);
}
setLoading(false);
}

async function handlePost(parentId: string | null = null) {
Expand Down Expand Up @@ -212,7 +217,7 @@ export function McpComments({ slug, isAuthenticated }: McpCommentsProps) {
)}

{error && (
<div className="mb-4 p-3 bg-red-500/10 border border-red-500/20 rounded-lg text-red-500 text-sm">
<div role="alert" className="mb-4 p-3 bg-red-500/10 border border-red-500/20 rounded-lg text-red-500 text-sm">
{error}
</div>
)}
Expand All @@ -221,7 +226,7 @@ export function McpComments({ slug, isAuthenticated }: McpCommentsProps) {
<div className="flex justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
) : comments.length > 0 ? (
) : error ? null : comments.length > 0 ? (
<div className="divide-y divide-border">
{comments.map((comment) => renderComment(comment))}
</div>
Expand Down
21 changes: 13 additions & 8 deletions src/components/prompts/PromptComments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,20 @@ export function PromptComments({ slug, isAuthenticated }: PromptCommentsProps) {
async function fetchComments() {
try {
const res = await fetch(`/api/prompts/${slug}/comments`);
if (res.ok) {
const data = await res.json();
setComments(data.comments || []);
setTotal(data.total || 0);
if (!res.ok) {
const data = await res.json().catch(() => ({}));
setError(data.error || "Failed to load comments");
return;
}

const data = await res.json();
setComments(data.comments || []);
setTotal(data.total || 0);
} catch {
// silently fail
setError("Failed to load comments");
} finally {
setLoading(false);
}
setLoading(false);
}

async function handlePost(parentId: string | null = null) {
Expand Down Expand Up @@ -212,7 +217,7 @@ export function PromptComments({ slug, isAuthenticated }: PromptCommentsProps) {
)}

{error && (
<div className="mb-4 p-3 bg-red-500/10 border border-red-500/20 rounded-lg text-red-500 text-sm">
<div role="alert" className="mb-4 p-3 bg-red-500/10 border border-red-500/20 rounded-lg text-red-500 text-sm">
{error}
</div>
)}
Expand All @@ -221,7 +226,7 @@ export function PromptComments({ slug, isAuthenticated }: PromptCommentsProps) {
<div className="flex justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
) : comments.length > 0 ? (
) : error ? null : comments.length > 0 ? (
<div className="divide-y divide-border">
{comments.map((comment) => renderComment(comment))}
</div>
Expand Down
21 changes: 13 additions & 8 deletions src/components/skills/SkillComments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,20 @@ export function SkillComments({ slug, isAuthenticated }: SkillCommentsProps) {
async function fetchComments() {
try {
const res = await fetch(`/api/skills/${slug}/comments`);
if (res.ok) {
const data = await res.json();
setComments(data.comments || []);
setTotal(data.total || 0);
if (!res.ok) {
const data = await res.json().catch(() => ({}));
setError(data.error || "Failed to load comments");
return;
}

const data = await res.json();
setComments(data.comments || []);
setTotal(data.total || 0);
} catch {
// silently fail
setError("Failed to load comments");
} finally {
setLoading(false);
}
setLoading(false);
}

async function handlePost(parentId: string | null = null) {
Expand Down Expand Up @@ -213,7 +218,7 @@ export function SkillComments({ slug, isAuthenticated }: SkillCommentsProps) {
)}

{error && (
<div className="mb-4 p-3 bg-red-500/10 border border-red-500/20 rounded-lg text-red-500 text-sm">
<div role="alert" className="mb-4 p-3 bg-red-500/10 border border-red-500/20 rounded-lg text-red-500 text-sm">
{error}
</div>
)}
Expand All @@ -222,7 +227,7 @@ export function SkillComments({ slug, isAuthenticated }: SkillCommentsProps) {
<div className="flex justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
) : comments.length > 0 ? (
) : error ? null : comments.length > 0 ? (
<div className="divide-y divide-border">
{comments.map((comment) => renderComment(comment))}
</div>
Expand Down