Files
OpenFrontIO/tests/UserSettings.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

138 lines
4.5 KiB
TypeScript

import {
EFFECTS_KEY,
PLAYER_STATS_COLUMNS_KEY,
TEAM_STATS_COLUMNS_KEY,
UserSettings,
} from "../src/core/game/UserSettings";
describe("UserSettings effect selection", () => {
beforeEach(() => {
localStorage.clear();
// UserSettings keeps a static in-memory cache; reset it too so each test
// reads fresh from the (cleared) localStorage.
(
UserSettings as unknown as { cache: Map<string, string | null> }
).cache.clear();
});
it("sets and reads a per-effectType selection", () => {
const s = new UserSettings();
s.setSelectedEffectName("transportShipTrail", "spectrum");
expect(s.getSelectedEffectName("transportShipTrail")).toBe("spectrum");
});
it("returns null when nothing is selected", () => {
expect(
new UserSettings().getSelectedEffectName("transportShipTrail"),
).toBeNull();
});
it("clearing the last selection removes the storage key", () => {
const s = new UserSettings();
s.setSelectedEffectName("transportShipTrail", "spectrum");
s.setSelectedEffectName("transportShipTrail", undefined);
expect(s.getSelectedEffectName("transportShipTrail")).toBeNull();
expect(localStorage.getItem(EFFECTS_KEY)).toBeNull();
});
it("clearing one effectType leaves other types intact", () => {
const s = new UserSettings();
// Seed two types directly (only one real effectType exists today).
localStorage.setItem(
EFFECTS_KEY,
JSON.stringify({ transportShipTrail: "spectrum", future: "x" }),
);
s.setSelectedEffectName("transportShipTrail", undefined);
expect(s.getSelectedEffects()).toEqual({ future: "x" });
});
it("returns an empty map for a corrupt blob", () => {
localStorage.setItem(EFFECTS_KEY, "not json");
expect(new UserSettings().getSelectedEffects()).toEqual({});
});
it("keeps per-nukeType nuke-explosion slots independent", () => {
const s = new UserSettings();
s.setSelectedEffectName("atom", "atom_boom");
s.setSelectedEffectName("hydro", "hydro_boom");
expect(s.getSelectedEffectName("atom")).toBe("atom_boom");
expect(s.getSelectedEffectName("hydro")).toBe("hydro_boom");
// Clearing one bomb's slot leaves the others intact.
s.setSelectedEffectName("atom", undefined);
expect(s.getSelectedEffectName("atom")).toBeNull();
expect(s.getSelectedEffectName("hydro")).toBe("hydro_boom");
});
});
describe("UserSettings stats columns", () => {
beforeEach(() => {
localStorage.clear();
(
UserSettings as unknown as { cache: Map<string, string | null> }
).cache.clear();
});
it("returns defaults when nothing is stored", () => {
expect(new UserSettings().statsColumns("player")).toEqual([
"tiles",
"gold",
"maxtroops",
]);
expect(new UserSettings().statsColumns("team")).toEqual([
"tiles",
"gold",
"maxtroops",
]);
});
it("round-trips a selection in registry order", () => {
const s = new UserSettings();
// Stored order is check order; getter returns registry (display) order.
s.setStatsColumns("player", ["warships", "gold"]);
expect(s.statsColumns("player")).toEqual(["gold", "warships"]);
});
it("filters unknown ids", () => {
localStorage.setItem(
PLAYER_STATS_COLUMNS_KEY,
JSON.stringify(["gold", "bogus"]),
);
expect(new UserSettings().statsColumns("player")).toEqual(["gold"]);
});
it("drops the removed attacks column from persisted selections", () => {
localStorage.setItem(
PLAYER_STATS_COLUMNS_KEY,
JSON.stringify(["attacks", "gold"]),
);
expect(new UserSettings().statsColumns("player")).toEqual(["gold"]);
});
it("falls back to defaults on corrupt JSON", () => {
localStorage.setItem(PLAYER_STATS_COLUMNS_KEY, "not json");
expect(new UserSettings().statsColumns("player")).toEqual([
"tiles",
"gold",
"maxtroops",
]);
});
it("falls back to defaults when no valid ids remain", () => {
localStorage.setItem(PLAYER_STATS_COLUMNS_KEY, JSON.stringify(["bogus"]));
expect(new UserSettings().statsColumns("player")).toEqual([
"tiles",
"gold",
"maxtroops",
]);
});
it("keeps player and team selections independent", () => {
const s = new UserSettings();
s.setStatsColumns("player", ["gold"]);
s.setStatsColumns("team", ["warships"]);
expect(s.statsColumns("player")).toEqual(["gold"]);
expect(s.statsColumns("team")).toEqual(["warships"]);
expect(localStorage.getItem(TEAM_STATS_COLUMNS_KEY)).toBe('["warships"]');
});
});