From 97eca25815f1bf39cf5ba40af5b110b68969ef9d Mon Sep 17 00:00:00 2001 From: Baskarayelu Date: Mon, 29 Jun 2026 13:15:13 +0530 Subject: [PATCH] feat: Polish Marketplace skeleton placeholders (#288) - Replace custom MarketplaceCardSkeleton with ApiCardSkeleton from ApiCard.tsx to achieve exact shape parity (same layout, stats section, and dimensions) - Fix broken JSX in ApiCard.tsx (missing closing div around sparkline row) - Fix missing Fragment import in Skeleton.tsx - Expand skeleton test suite from 1 to 3 focused tests covering card structure, header, sidebar, and toolbar parity Co-Authored-By: Claude Sonnet 4.6 --- src/components/ApiCard.tsx | 1 + src/components/Skeleton.tsx | 2 +- src/pages/MarketplacePage.skeleton.test.tsx | 23 +++++++++ src/pages/MarketplacePage.skeleton.tsx | 56 +++++++++++++-------- 4 files changed, 59 insertions(+), 23 deletions(-) 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 ( ); @@ -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 ── */}
+ {/* Toolbar: result count + actions */}
+ {/* + * Card grid — ApiCardSkeleton is kept in sync with the real ApiCard + * layout automatically, so any future card changes are reflected here. + */}
{Array.from({ length: 6 }).map((_, index) => ( - + ))}