Files
OpenFrontIO/tests/GameLeftSidebar.test.ts
T
RyanandGitHub d76a0c4009 feat(client): configurable leaderboard and team stats columns (#4646)
## Description:

- rips out some duped code, making tables use the same layout, where the
only thing different is the row content.
- updated text to be emojis to save space
- updated columns to be as wide as they need to be
- updated cells to have left/right thin borders to read easier
- added more supported column types
- removed the + button to always show everyone, where the current player
is either at the bottom or in the top 5
- added a cogwheel to change options
- can select different options for each menu type (player/team stats)

<img width="582" height="630" alt="image"
src="https://github.com/user-attachments/assets/e54dc3d5-eb2c-4bab-8732-06bea68aec45"
/>

works fine on mobile, will probably need some future love though (has
h-scroll, so all options can be seen by scrolling):


## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory

## Please put your Discord username so you can be contacted if a bug or
regression is found:

w.o.n
2026-07-22 13:13:43 -07:00

104 lines
3.6 KiB
TypeScript

import { GameLeftSidebar } from "../src/client/hud/layers/GameLeftSidebar";
import type { PlayerStats } from "../src/client/hud/layers/PlayerStats";
import type { TeamStats } from "../src/client/hud/layers/TeamStats";
import type { GameView, PlayerView } from "../src/client/view";
import { EventBus } from "../src/core/EventBus";
import { GameMode } from "../src/core/game/Game";
import { UserSettings } from "../src/core/game/UserSettings";
describe("GameLeftSidebar", () => {
beforeEach(() => {
localStorage.clear();
(
UserSettings as unknown as { cache: Map<string, string | null> }
).cache.clear();
});
it("owns the player and team stats tables", async () => {
const game = {
config: () => ({ gameConfig: () => ({ gameMode: GameMode.Team }) }),
} as unknown as GameView;
const eventBus = new EventBus();
const sidebar = new GameLeftSidebar();
sidebar.game = game;
sidebar.eventBus = eventBus;
document.body.append(sidebar);
await sidebar.updateComplete;
const playerStats = sidebar.querySelector("player-stats");
const teamStats = sidebar.querySelector("team-stats");
expect(typeof (playerStats as PlayerStats).refresh).toBe("function");
expect(typeof (teamStats as TeamStats).refresh).toBe("function");
expect((playerStats as PlayerStats).game).toBe(game);
expect((playerStats as PlayerStats).eventBus).toBe(eventBus);
expect((teamStats as TeamStats).game).toBe(game);
sidebar.remove();
});
it("renders the player stats table after toggling it", async () => {
const player = {
id: () => "player-1",
displayName: () => "Player 1",
numTilesOwned: () => 10,
gold: () => 100n,
isAlive: () => true,
isOnSameTeam: () => false,
team: () => null,
} as unknown as PlayerView;
const game = {
config: () => ({
gameConfig: () => ({ gameMode: GameMode.FFA }),
maxTroops: () => 1_000,
}),
gameID: () => "test-game",
inSpawnPhase: () => false,
myPlayer: () => player,
numLandTiles: () => 100,
numTilesWithFallout: () => 0,
playerViews: () => [player],
} as unknown as GameView;
const sidebar = new GameLeftSidebar();
sidebar.game = game;
sidebar.eventBus = new EventBus();
document.body.append(sidebar);
sidebar.init();
await sidebar.updateComplete;
(sidebar.querySelector('[role="button"]') as HTMLElement).click();
await sidebar.updateComplete;
const playerStats = sidebar.querySelector("player-stats") as PlayerStats;
await playerStats.updateComplete;
expect(playerStats.visible).toBe(true);
expect(playerStats.querySelector(".stats-table")).not.toBeNull();
expect(() => sidebar.tick()).not.toThrow();
await playerStats.updateComplete;
expect(playerStats.querySelector(".stats-table-row")).not.toBeNull();
sidebar.remove();
});
it("stacks the player and team stats tables vertically at every width", async () => {
const game = {
config: () => ({ gameConfig: () => ({ gameMode: GameMode.Team }) }),
} as unknown as GameView;
const sidebar = new GameLeftSidebar();
sidebar.game = game;
sidebar.eventBus = new EventBus();
document.body.append(sidebar);
await sidebar.updateComplete;
const wrapper = sidebar.querySelector("player-stats")?.parentElement;
expect(wrapper?.classList).toContain("flex-col");
expect(wrapper?.className).not.toMatch(/flex-wrap|lg:flex/);
expect(sidebar.querySelector("team-stats")?.className).not.toContain(
"lg:flex-initial",
);
sidebar.remove();
});
});