From 5a3bbbed0e811cd8a2fae9f89f3fc81917dd9b67 Mon Sep 17 00:00:00 2001 From: greatest0fallt1me Date: Sat, 24 Oct 2026 15:57:37 +0530 Subject: [PATCH] fix(rate-limit): finish per-API rate limit wiring broken by #1356 #1356 was squashed with only part of its changes, leaving main failing type-check: App.tsx used useToast/Navigate without importing them and referenced APP_ROUTES.legacyRateLimit, and RateLimitCard referenced an undefined `api`. Its test file was a broken merge of old and new tests. - App: import Navigate and useToast, route /marketplace/:apiId/rate-limits to RateLimitCard and redirect legacy /rate-limit to the Marketplace with a one-time toast. - RateLimitCard: resolve the API from fetchCatalog by :apiId, build the table/cards from api.rateLimits, hide empty optional columns, and handle loading, not-published, not-found and error states. - renderRoute: use isMarketplaceSubpage so per-API subpages reach App. - ApiDetailPage: add a "View rate limits" link next to "View SLA". - Tests: rewrite RateLimitCard tests, point route-splitting test at the per-API path. Co-Authored-By: Claude Opus 5.5 --- src/App.tsx | 10 +- src/RouteSplitting.test.tsx | 2 +- src/pages/ApiDetailPage.tsx | 9 + src/pages/RateLimitCard.test.tsx | 255 ++++++++++--------------- src/pages/RateLimitCard.tsx | 316 +++++++++++++++++-------------- src/renderRoute.tsx | 5 +- 6 files changed, 289 insertions(+), 308 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index efead44d..ed93e738 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import { useEffect, useState, useCallback, useMemo, useRef, lazy, Suspense } from "react"; -import { Routes, Route, NavLink, useNavigate, useLocation } from "react-router-dom"; +import { Routes, Route, NavLink, Navigate, useNavigate, useLocation } from "react-router-dom"; import { ThemeToggle } from "./ThemeToggle"; import ServerError from "./components/ServerError"; import useDocumentTitle from "./hooks/useDocumentTitle"; @@ -24,6 +24,7 @@ import { useSessionExpiry } from "./hooks/useSessionExpiry"; import SessionExpiryBanner from "./components/SessionExpiryBanner"; import WalletStatus from "./components/WalletStatus"; import { useWalletStore } from "./state/walletStore"; +import { useToast } from "./components/Toast"; // Route splitting: dynamic imports for all heavy page routes const MarketplacePage = lazy(() => import("./pages/MarketplacePage")); @@ -216,7 +217,8 @@ const APP_ROUTES = { themePlayground: "/theme-playground", designSystem: "/design-system/docs", serverError: "/500", - rateLimitCard: "/rate-limit", + rateLimitCard: "/marketplace/:apiId/rate-limits", + legacyRateLimit: "/rate-limit", slaCard: "/marketplace/:apiId/sla", webhookDeliveries: "/webhooks/deliveries", onboarding: "/onboarding", @@ -235,7 +237,11 @@ const PRIMARY_NAV: Array<{ to: string; label: string }> = [ /** `/rate-limit` no longer exists; limits are per-API. Send users to the Marketplace. */ function LegacyRateLimitRedirect() { const { showToast } = useToast(); + const announced = useRef(false); useEffect(() => { + // Guard against StrictMode's double effect invocation in development. + if (announced.current) return; + announced.current = true; showToast("Rate limits are now shown per API. Pick an API to see its limits."); }, [showToast]); return ; diff --git a/src/RouteSplitting.test.tsx b/src/RouteSplitting.test.tsx index 70263800..ffb4c2e5 100644 --- a/src/RouteSplitting.test.tsx +++ b/src/RouteSplitting.test.tsx @@ -70,7 +70,7 @@ describe('Route Splitting and Responsiveness Suite (Quality-2)', () => { }); it('dynamically loads Rate Limit Card route inside Suspense boundary', async () => { - renderApp('/rate-limit'); + renderApp('/marketplace/weather-001/rate-limits'); await waitFor(() => { expect(screen.getByRole('heading', { name: /Rate Limit Configuration/i })).toBeTruthy(); }, { timeout: 4000 }); diff --git a/src/pages/ApiDetailPage.tsx b/src/pages/ApiDetailPage.tsx index 3b87a631..5f9c1551 100644 --- a/src/pages/ApiDetailPage.tsx +++ b/src/pages/ApiDetailPage.tsx @@ -1325,6 +1325,15 @@ export default function ApiDetailPage({ onBack }: Props) { View SLA +
+ Rate limits + + View rate limits + +
diff --git a/src/pages/RateLimitCard.test.tsx b/src/pages/RateLimitCard.test.tsx index a9874818..a42bb818 100644 --- a/src/pages/RateLimitCard.test.tsx +++ b/src/pages/RateLimitCard.test.tsx @@ -1,30 +1,48 @@ // @vitest-environment jsdom -import { cleanup, render, screen } from "@testing-library/react"; -import { afterEach, describe, expect, it, vi } from "vitest"; -import { MemoryRouter } from "react-router-dom"; -import RateLimitCard, { type RateLimitCardProps } from "./RateLimitCard"; +import { cleanup, render, screen, within } from "@testing-library/react"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { MemoryRouter, Route, Routes } from "react-router-dom"; +import RateLimitCard, { PLAN_PATTERNS, type RateLimitCardProps } from "./RateLimitCard"; +import type { APIItem } from "../data/mockApis"; +import { ROUTE_CATALOGUE } from "../config/routeCatalogue"; /** - * RateLimitCard tests – issue #567 (GrantFox FWC26 "Stellar Wave") - * and mobile responsive stacked plan cards. + * RateLimitCard tests – per-API rate limits at `/marketplace/:apiId/rate-limits`. * - * Covers: - * 1. Mobile layout: four plan cards, no table - * 2. Desktop layout: table with four body rows - * 3. currentPlan prop: badge and aria-current on card or row - * 4. Numbers: non-breaking tabular-nums span - * 5. Pattern text: exposed to assistive tech - * 6. Style injection: no inline