diff --git a/src/components/MarketplaceComments.test.tsx b/src/components/MarketplaceComments.test.tsx new file mode 100644 index 00000000..30dc4f95 --- /dev/null +++ b/src/components/MarketplaceComments.test.tsx @@ -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: () => , + }, + { + name: "MCP", + render: () => , + }, + { + name: "prompt", + render: () => , + }, + { + name: "skill", + render: () => , + }, +]; + +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(); + }); +}); diff --git a/src/components/directory/DirectoryComments.tsx b/src/components/directory/DirectoryComments.tsx index dfd6b74b..9abaee41 100644 --- a/src/components/directory/DirectoryComments.tsx +++ b/src/components/directory/DirectoryComments.tsx @@ -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) { @@ -213,7 +218,7 @@ export function DirectoryComments({ listingId, isAuthenticated }: DirectoryComme )} {error && ( -
+
{error}
)} @@ -222,7 +227,7 @@ export function DirectoryComments({ listingId, isAuthenticated }: DirectoryComme
- ) : comments.length > 0 ? ( + ) : error ? null : comments.length > 0 ? (
{comments.map((comment) => renderComment(comment))}
diff --git a/src/components/mcp/McpComments.tsx b/src/components/mcp/McpComments.tsx index ca2053d6..5b551dba 100644 --- a/src/components/mcp/McpComments.tsx +++ b/src/components/mcp/McpComments.tsx @@ -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) { @@ -212,7 +217,7 @@ export function McpComments({ slug, isAuthenticated }: McpCommentsProps) { )} {error && ( -
+
{error}
)} @@ -221,7 +226,7 @@ export function McpComments({ slug, isAuthenticated }: McpCommentsProps) {
- ) : comments.length > 0 ? ( + ) : error ? null : comments.length > 0 ? (
{comments.map((comment) => renderComment(comment))}
diff --git a/src/components/prompts/PromptComments.tsx b/src/components/prompts/PromptComments.tsx index 1d91a471..832bf297 100644 --- a/src/components/prompts/PromptComments.tsx +++ b/src/components/prompts/PromptComments.tsx @@ -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) { @@ -212,7 +217,7 @@ export function PromptComments({ slug, isAuthenticated }: PromptCommentsProps) { )} {error && ( -
+
{error}
)} @@ -221,7 +226,7 @@ export function PromptComments({ slug, isAuthenticated }: PromptCommentsProps) {
- ) : comments.length > 0 ? ( + ) : error ? null : comments.length > 0 ? (
{comments.map((comment) => renderComment(comment))}
diff --git a/src/components/skills/SkillComments.tsx b/src/components/skills/SkillComments.tsx index 9f09bb38..f06a40a8 100644 --- a/src/components/skills/SkillComments.tsx +++ b/src/components/skills/SkillComments.tsx @@ -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) { @@ -213,7 +218,7 @@ export function SkillComments({ slug, isAuthenticated }: SkillCommentsProps) { )} {error && ( -
+
{error}
)} @@ -222,7 +227,7 @@ export function SkillComments({ slug, isAuthenticated }: SkillCommentsProps) {
- ) : comments.length > 0 ? ( + ) : error ? null : comments.length > 0 ? (
{comments.map((comment) => renderComment(comment))}