Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/guides/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
4 changes: 3 additions & 1 deletion docs/subsystems/challenges.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/components/StandingChallenges.js
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ function StandingChallenges(props) {
header: t("tables.game"),
sortingFn: stringColumnSortingFn(i18n.language),
cell: (props) => (
<Link to={`/challenges/${props.row.original.metaGame}`}>
<Link to={`/games/${props.row.original.metaGame}`}>
{props.getValue()}
</Link>
),
Expand Down
57 changes: 57 additions & 0 deletions src/components/StandingChallenges.test.js
Original file line number Diff line number Diff line change
@@ -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(
<HelmetProvider>
<MemoryRouter initialEntries={["/challenges"]}>
<StandingChallenges />
</MemoryRouter>
</HelmetProvider>
);

const link = await screen.findByRole("link", { name: "Homeworlds" });
expect(link).toHaveAttribute("href", "/games/homeworlds");
});
});