diff --git a/src/App.tsx b/src/App.tsx index efead44..ed93e73 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 7026380..ffb4c2e 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 3b87a63..5f9c155 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 a987481..a42bb81 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