diff --git a/src/components/ApiCard.tsx b/src/components/ApiCard.tsx
index 71ad9f3..a651d78 100644
--- a/src/components/ApiCard.tsx
+++ b/src/components/ApiCard.tsx
@@ -543,6 +543,7 @@ export default function ApiCard({
width={90}
height={28}
/>
+
{
expect(container.querySelector(".marketplace-grid")).toBeTruthy();
expect(container.querySelectorAll(".api-marketplace-card").length).toBe(6);
});
+
+ it("renders card skeletons that match the ApiCard structure for shape parity", () => {
+ const { container } = render(
);
+
+ // Each card skeleton should contain the stats section matching the real card
+ const cards = container.querySelectorAll(".api-marketplace-card");
+ expect(cards.length).toBe(6);
+
+ // Each card should have a stats section (matching real ApiCard structure)
+ cards.forEach((card) => {
+ expect(card.querySelector(".api-card__stats")).toBeTruthy();
+ expect(card.querySelectorAll(".api-card__stat").length).toBe(3);
+ });
+ });
+
+ it("renders header, filter sidebar, and toolbar placeholders", () => {
+ const { container } = render(
);
+
+ expect(container.querySelector(".marketplace-header")).toBeTruthy();
+ expect(container.querySelector(".marketplace-sidebar")).toBeTruthy();
+ expect(container.querySelector(".marketplace-toolbar")).toBeTruthy();
+ expect(container.querySelector(".filters-sidebar")).toBeTruthy();
+ });
});
diff --git a/src/pages/MarketplacePage.skeleton.tsx b/src/pages/MarketplacePage.skeleton.tsx
index edf14c9..5e5d44d 100644
--- a/src/pages/MarketplacePage.skeleton.tsx
+++ b/src/pages/MarketplacePage.skeleton.tsx
@@ -1,26 +1,22 @@
+/**
+ * MarketplacePage.skeleton.tsx
+ *
+ * Loading shell for the Marketplace page.
+ * Uses ApiCardSkeleton (from ApiCard.tsx) to keep skeleton shapes in
+ * exact parity with the real cards — dimensions, layout structure, and
+ * spacing all mirror the final rendered output so the CLS shift on load
+ * is minimal.
+ */
import Skeleton from "../components/Skeleton";
+import { ApiCardSkeleton } from "../components/ApiCard";
-function MarketplaceCardSkeleton() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
+/**
+ * Mirrors the FiltersSidebar structure:
+ * - heading placeholder
+ * - 4 filter groups (label + input)
+ * - price range row
+ * - apply button
+ */
function FilterBlockSkeleton() {
return (
+ {/* Section heading */}
+ {/* Filter groups */}
{Array.from({ length: 4 }).map((_, index) => (
))}
+ {/* Price range label */}
+ {/* Apply / clear button */}
);
@@ -48,9 +48,12 @@ export default function MarketplacePageSkeleton() {
aria-busy="true"
aria-label="Marketplace loading shell"
>
+ {/* ── Header row ── */}
+ {/* Page title */}
+ {/* Search + density toggle */}
@@ -64,26 +67,35 @@ export default function MarketplacePageSkeleton() {
+ {/* Sort dropdown */}
+ {/* ── Main layout ── */}