From 4ca3930c92d8df30fb4c6f2dfc6abc73967b4430 Mon Sep 17 00:00:00 2001 From: norendren Date: Sat, 7 Nov 2026 12:30:35 -0700 Subject: [PATCH] Link open challenges game names to the game page The site-wide list linked each game to /challenges/:metaGame, a filtered copy of the list. Link to /games/:metaGame instead so players browsing challenges can read about a game; its Challenges tab shows the same list. --- CHANGELOG.md | 3 +- docs/guides/routing.md | 2 +- docs/subsystems/challenges.md | 4 +- src/components/StandingChallenges.js | 2 +- src/components/StandingChallenges.test.js | 57 +++++++++++++++++++++++ 5 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 src/components/StandingChallenges.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index fbbf08f5..0f93f18e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ Since the `1.0.0-beta` release, the version in `package.json` has stayed at `1.0 **Monthly batching (agents):** Use **one** `## [1.0.0-ci] - YYYY-MM-DD` header per calendar month. Before editing, search this file for `- YYYY-MM-` (today’s year-month); if a section exists, append bullets under `### Added` / `### Changed` / `### Fixed` there and set that header’s date to today — do **not** add a second `##` for the same month. A new ship day is not a new section. -## [1.0.0-ci] - 2026-11-06 +## [1.0.0-ci] - 2026-11-07 ### Added @@ -20,6 +20,7 @@ Since the `1.0.0-beta` release, the version in `package.json` has stayed at `1.0 ### Changed - **Dashboard tables:** tournament game rows no longer use bold text; the Event column badge identifies ladder, tournament, and organized-event games. +- **Open challenges:** clicking a game name in the site-wide list now opens the game page instead of a list of that game's challenges, which remain available on the game page's **Challenges** tab. ### Fixed diff --git a/docs/guides/routing.md b/docs/guides/routing.md index 15a69778..68d5b86d 100644 --- a/docs/guides/routing.md +++ b/docs/guides/routing.md @@ -10,7 +10,7 @@ All routes are defined in [`src/pages/Skeleton.js`](../src/pages/Skeleton.js) in | `/move/:metaGame/:cbits/:gameID` | `GameMoveWrapper` | Live game | | `/games/:metaGame?` | `Explore` | Lazy-loaded | | `/player/:userid` | `Player` | Lazy-loaded | -| `/challenges/:metaGame` | `StandingChallenges` | | +| `/challenges/:metaGame?` | `StandingChallenges` | Site-wide list when `:metaGame` is omitted | | `/listgames/:gameState/:metaGame` | `ListGames` | | | `/ratings/:metaGame` | `Ratings` | | | `/tournaments/:tab?/:metaGame?` | `Tournaments` | Tabbed list (`open`, `current`, `completed`); optional game filter | diff --git a/docs/subsystems/challenges.md b/docs/subsystems/challenges.md index 92ebc731..c0df6385 100644 --- a/docs/subsystems/challenges.md +++ b/docs/subsystems/challenges.md @@ -1,6 +1,8 @@ # Challenges -Standing challenges let players post open invitations for a specific meta-game. Route: `/challenges/:metaGame`. +Standing challenges let players post open invitations for a specific meta-game. Routes: `/challenges` (site-wide) and `/challenges/:metaGame` (single game). + +In the site-wide list, the game name links to the game page (`/games/:metaGame`) so players browsing challenges can read about a game. That page's **Challenges** tab lists the same per-game challenges. ## Key files diff --git a/src/components/StandingChallenges.js b/src/components/StandingChallenges.js index 306f237a..76604fed 100644 --- a/src/components/StandingChallenges.js +++ b/src/components/StandingChallenges.js @@ -565,7 +565,7 @@ function StandingChallenges(props) { header: t("tables.game"), sortingFn: stringColumnSortingFn(i18n.language), cell: (props) => ( - + {props.getValue()} ), diff --git a/src/components/StandingChallenges.test.js b/src/components/StandingChallenges.test.js new file mode 100644 index 00000000..4c369fb1 --- /dev/null +++ b/src/components/StandingChallenges.test.js @@ -0,0 +1,57 @@ +import { render, screen } from "@testing-library/react"; +import { HelmetProvider } from "react-helmet-async"; +import { MemoryRouter } from "react-router-dom"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import StandingChallenges from "./StandingChallenges"; +import { useStore } from "../stores"; + +vi.mock("react-i18next", async (importOriginal) => ({ + ...(await importOriginal()), + useTranslation: () => ({ + t: (key) => key, + i18n: { language: "en", changeLanguage: vi.fn() }, + }), +})); + +vi.mock("../hooks/useAuthSession", () => ({ + useAuthSession: () => ({ status: "anonymous" }), +})); + +vi.mock("./Me/ChallengeResponseModal", () => ({ default: () => null })); + +const challenge = { + id: "challenge-1", + metaGame: "homeworlds", + challenger: { id: "player-1", name: "Player One" }, + players: [{ id: "player-1", name: "Player One" }], + variants: [], + numPlayers: 2, + seating: "random", + rated: true, + clockStart: 72, + clockInc: 24, + clockMax: 240, +}; + +describe("StandingChallenges (site-wide)", () => { + beforeEach(() => { + useStore.setState({ globalMe: null, users: [] }); + vi.stubGlobal( + "fetch", + vi.fn(async () => new Response(JSON.stringify([challenge]))) + ); + }); + + it("links the game name to the game page", async () => { + render( + + + + + + ); + + const link = await screen.findByRole("link", { name: "Homeworlds" }); + expect(link).toHaveAttribute("href", "/games/homeworlds"); + }); +});