From 70b03a9de4a784ac035087b380628321f8b81610 Mon Sep 17 00:00:00 2001 From: Sanjay Krishnan K <196655178+Sanjaykris20@users.noreply.github.com> Date: Sat, 17 Oct 2026 21:48:50 +0530 Subject: [PATCH] fix(leaderboard): render empty search state with clear filter button --- .../components/leaderboard-table.tsx | 175 ++++++++++-------- .../tests/leaderboard-table.test.tsx | 66 ++++++- src/locales/ar.json | 2 + src/locales/en.json | 2 + 4 files changed, 167 insertions(+), 78 deletions(-) diff --git a/src/features/leaderboard/components/leaderboard-table.tsx b/src/features/leaderboard/components/leaderboard-table.tsx index 278edee..5b43bf5 100644 --- a/src/features/leaderboard/components/leaderboard-table.tsx +++ b/src/features/leaderboard/components/leaderboard-table.tsx @@ -7,6 +7,7 @@ import { AlertTriangle, ExternalLink, ArrowUpDown, ArrowUp, ArrowDown } from "lu import { Avatar } from "@/components/layout/avatar"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; import { useTranslation } from "@/components/providers/language-provider"; import { cn } from "@/utils/cn"; import type { ScoredLeaderboardEntry as LeaderboardEntry } from "../types"; @@ -266,86 +267,108 @@ export function LeaderboardTable({ - {filtered.map((user) => ( - - - - {user.impactRank} - + {filtered.length === 0 && search.trim() ? ( + + +
+

+ {t("leaderboard.noDevelopersSearch", { + query: search, + })} +

+ +
- - {(() => { - const profileUrl = ( - countrySlug - ? `/user/${user.username}?country=${encodeURIComponent(countrySlug)}` - : `/user/${user.username}` - ) as Route; + + ) : ( + filtered.map((user) => ( + + + + {user.impactRank} + + + + {(() => { + const profileUrl = ( + countrySlug + ? `/user/${user.username}?country=${encodeURIComponent(countrySlug)}` + : `/user/${user.username}` + ) as Route; - return ( -
- - - -
-
- - {user.name || user.username} - - - - + return ( +
+ + + +
+
+ + {user.name || user.username} + + + + +
+

+ @{user.username} +

-

- @{user.username} -

-
- ); - })()} - - - {user.finalScore} - - {user.repoScore} - {user.prScore} - - {user.contributionScore} - - - ))} + ); + })()} + + + {user.finalScore} + + {user.repoScore} + {user.prScore} + + {user.contributionScore} + + + )) + )}
diff --git a/src/features/leaderboard/tests/leaderboard-table.test.tsx b/src/features/leaderboard/tests/leaderboard-table.test.tsx index ade36d2..df015f3 100644 --- a/src/features/leaderboard/tests/leaderboard-table.test.tsx +++ b/src/features/leaderboard/tests/leaderboard-table.test.tsx @@ -17,7 +17,7 @@ vi.mock("next/link", () => ({ // Mock translations vi.mock("@/components/providers/language-provider", () => ({ useTranslation: () => ({ - t: (key: string) => { + t: (key: string, params?: Record) => { const translations: Record = { "leaderboard.title": "Leaderboard", "leaderboard.description": "Leaderboard description", @@ -25,6 +25,8 @@ vi.mock("@/components/providers/language-provider", () => ({ "leaderboard.impactRank": "Impact Rank", "leaderboard.search": "Search", "leaderboard.partialErrors": "Partial Errors", + "leaderboard.noDevelopersSearch": 'No developers found matching "{query}".', + "leaderboard.clearSearch": "Clear search", "comparison.final.score": "Final Score", "comparison.repo.score": "Repo Score", "comparison.pr.score": "PR Score", @@ -32,7 +34,12 @@ vi.mock("@/components/providers/language-provider", () => ({ "a11y.openProfile": "Open profile", }; - return translations[key] ?? key; + const template = translations[key] ?? key; + if (!params) return template; + return Object.keys(params).reduce( + (acc, k) => acc.split(`{${k}}`).join(String(params[k])), + template, + ); }, }), })); @@ -277,3 +284,58 @@ describe("LeaderboardTable keyboard shortcuts", () => { expect(document.activeElement).not.toBe(search); }); }); + +describe("LeaderboardTable empty search state", () => { + afterEach(() => { + cleanup(); + }); + + const users = [ + { + username: "alice", + name: "Alice", + avatarUrl: "https://example.com/alice.png", + impactRank: 1, + finalScore: 80, + repoScore: 30, + prScore: 20, + contributionScore: 40, + }, + ]; + + const renderLeaderboard = () => + render( + , + ); + + test("shows empty search state when query matches no developers", async () => { + const user = userEvent.setup(); + renderLeaderboard(); + + const searchInput = screen.getByPlaceholderText("Search"); + await user.type(searchInput, "nonexistent"); + + expect(screen.getByText('No developers found matching "nonexistent".')).toBeDefined(); + expect(screen.getByRole("button", { name: /clear search/i })).toBeDefined(); + }); + + test("clicking clear search resets the search input and restores developers", async () => { + const user = userEvent.setup(); + renderLeaderboard(); + + const searchInput = screen.getByPlaceholderText("Search") as HTMLInputElement; + await user.type(searchInput, "nonexistent"); + + const clearButton = screen.getByRole("button", { name: /clear search/i }); + await user.click(clearButton); + + expect(searchInput.value).toBe(""); + expect(screen.getByText("Alice")).toBeDefined(); + }); +}); diff --git a/src/locales/ar.json b/src/locales/ar.json index b440516..5a3b87b 100644 --- a/src/locales/ar.json +++ b/src/locales/ar.json @@ -258,6 +258,8 @@ "leaderboard.empty.title": "لم يتم العثور على دول", "leaderboard.empty.description": "ستظهر الدول المتاحة هنا.", "leaderboard.noCountriesSearch": "لا توجد دول تطابق بحثك.", + "leaderboard.noDevelopersSearch": "لم يتم العثور على مطورين يطابقون \"{query}\".", + "leaderboard.clearSearch": "مسح البحث", "leaderboard.noDevelopersFor": "لم يتم حساب قائمة المتصدرين في هذه الدولة بعد.", "leaderboard.error.retry": "حاول مرة أخرى", "leaderboard.viewCountry": "عرض لوحة المتصدرين", diff --git a/src/locales/en.json b/src/locales/en.json index 30a903f..3218e28 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -258,6 +258,8 @@ "leaderboard.empty.title": "No countries found", "leaderboard.empty.description": "Available countries will appear here.", "leaderboard.noCountriesSearch": "No countries match your search.", + "leaderboard.noDevelopersSearch": "No developers found matching \"{query}\".", + "leaderboard.clearSearch": "Clear search", "leaderboard.noDevelopersFor": "No leaderboard has been calculated for this country yet.", "leaderboard.error.retry": "Try again", "leaderboard.viewCountry": "View leaderboard",