mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-19 02:04:39 +00:00
convert stats modal to embed instead of popup (#4626)
## Description: convert stats modal to embed instead of popup before: <img width="1193" height="939" alt="image" src="https://github.com/user-attachments/assets/4f96843a-3b96-4874-8292-e670181bd87c" /> after: <img width="1226" height="863" alt="image" src="https://github.com/user-attachments/assets/801273cc-77cd-41b6-bc77-4fdc65afef71" /> also has context to remember where we were in the scrolled list, so pressing "back" keeps the position ## 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
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("../../src/client/Api", () => ({
|
||||
fetchPlayerById: vi.fn(async () => ({ stats: {} })),
|
||||
fetchPublicPlayerGames: vi.fn(async () => ({
|
||||
results: [
|
||||
{
|
||||
gameId: "game-1",
|
||||
start: "2026-07-01T12:00:00.000Z",
|
||||
durationSeconds: 600,
|
||||
map: "World",
|
||||
mode: "FFA",
|
||||
type: "public",
|
||||
playerTeams: null,
|
||||
rankedType: "",
|
||||
result: "victory",
|
||||
totalPlayers: 8,
|
||||
username: "Player",
|
||||
clanTag: null,
|
||||
},
|
||||
],
|
||||
nextCursor: null,
|
||||
})),
|
||||
getUserMe: vi.fn(async () => ({
|
||||
user: { email: "player@example.com" },
|
||||
player: {
|
||||
publicId: "player-1",
|
||||
friends: [],
|
||||
rewards: [],
|
||||
clans: [],
|
||||
clanRequests: [],
|
||||
},
|
||||
})),
|
||||
invalidateUserMe: vi.fn(),
|
||||
setMarketingConsent: vi.fn(async () => true),
|
||||
}));
|
||||
|
||||
vi.mock("../../src/client/Auth", () => ({
|
||||
discordLogin: vi.fn(),
|
||||
googleLogin: vi.fn(),
|
||||
linkGoogle: vi.fn(),
|
||||
logOut: vi.fn(),
|
||||
reauthAfterCrazyGamesChange: vi.fn(),
|
||||
sendMagicLink: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../src/client/Cosmetics", () => ({
|
||||
fetchCosmetics: vi.fn(async () => null),
|
||||
}));
|
||||
|
||||
vi.mock("../../src/client/CrazyGamesSDK", () => ({
|
||||
crazyGamesSDK: {
|
||||
getUserProfile: vi.fn(async () => null),
|
||||
isOnCrazyGames: vi.fn(() => false),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("src/client/ClientEnv", () => ({
|
||||
ClientEnv: { workerPath: vi.fn(() => "w0") },
|
||||
}));
|
||||
|
||||
vi.mock("../../src/client/TerrainMapFileLoader", () => ({
|
||||
terrainMapFileLoader: {
|
||||
getMapData: vi.fn(() => ({ webpPath: "/maps/world.webp" })),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../../src/client/Utils", () => ({
|
||||
getMapName: vi.fn((name: string) => name),
|
||||
renderDuration: vi.fn((seconds: number) => `${seconds}s`),
|
||||
translateText: vi.fn((key: string) => key),
|
||||
}));
|
||||
|
||||
vi.mock("../../src/client/components/baseComponents/stats/GameInfoView", () => {
|
||||
class FakeGameInfoView extends HTMLElement {}
|
||||
if (!customElements.get("game-info-view")) {
|
||||
customElements.define("game-info-view", FakeGameInfoView);
|
||||
}
|
||||
return { GameInfoView: FakeGameInfoView };
|
||||
});
|
||||
|
||||
vi.mock(
|
||||
"../../src/client/components/baseComponents/stats/DiscordUserHeader",
|
||||
() => ({}),
|
||||
);
|
||||
vi.mock(
|
||||
"../../src/client/components/baseComponents/stats/PlayerStatsTable",
|
||||
() => ({}),
|
||||
);
|
||||
vi.mock(
|
||||
"../../src/client/components/baseComponents/stats/PlayerStatsTree",
|
||||
() => ({}),
|
||||
);
|
||||
vi.mock("../../src/client/components/CopyButton", () => ({}));
|
||||
vi.mock("../../src/client/components/CurrencyDisplay", () => ({}));
|
||||
vi.mock("../../src/client/components/Difficulties", () => ({}));
|
||||
vi.mock("../../src/client/components/FriendsList", () => ({}));
|
||||
vi.mock("../../src/client/components/RewardsPanel", () => ({}));
|
||||
vi.mock("../../src/client/components/SubscriptionPanel", () => ({}));
|
||||
|
||||
class FakeIntersectionObserver {
|
||||
constructor(_callback: IntersectionObserverCallback) {}
|
||||
observe() {}
|
||||
disconnect() {}
|
||||
unobserve() {}
|
||||
takeRecords() {
|
||||
return [];
|
||||
}
|
||||
root = null;
|
||||
rootMargin = "";
|
||||
thresholds = [];
|
||||
}
|
||||
vi.stubGlobal("IntersectionObserver", FakeIntersectionObserver);
|
||||
|
||||
import { AccountModal } from "../../src/client/AccountModal";
|
||||
import { fetchPublicPlayerGames } from "../../src/client/Api";
|
||||
import { modalRouter } from "../../src/client/ModalRouter";
|
||||
|
||||
type ModalShell = HTMLElement & {
|
||||
getScrollTop(): number;
|
||||
setScrollTop(value: number): void;
|
||||
updateComplete: Promise<boolean>;
|
||||
};
|
||||
|
||||
async function waitForModal(
|
||||
modal: AccountModal,
|
||||
assertion: () => void,
|
||||
): Promise<void> {
|
||||
await vi.waitFor(async () => {
|
||||
await modal.updateComplete;
|
||||
const shell = modal.querySelector("o-modal") as ModalShell | null;
|
||||
await shell?.updateComplete;
|
||||
const history = modal.querySelector("player-game-history-view") as
|
||||
| (HTMLElement & { updateComplete: Promise<boolean> })
|
||||
| null;
|
||||
await history?.updateComplete;
|
||||
assertion();
|
||||
});
|
||||
}
|
||||
|
||||
describe("Account Games stats navigation", () => {
|
||||
let modal: AccountModal;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
history.replaceState(null, "", "/");
|
||||
modalRouter.register("account", { tag: "account-modal" });
|
||||
if (!customElements.get("account-modal")) {
|
||||
customElements.define("account-modal", AccountModal);
|
||||
}
|
||||
modal = document.createElement("account-modal") as AccountModal;
|
||||
document.body.appendChild(modal);
|
||||
await modal.updateComplete;
|
||||
modal.open();
|
||||
await waitForModal(modal, () => {
|
||||
const shell = modal.querySelector("o-modal") as ModalShell;
|
||||
expect(shell.shadowRoot?.textContent).toContain(
|
||||
"account_modal.tab_games",
|
||||
);
|
||||
});
|
||||
modal.setActiveTab("games");
|
||||
await waitForModal(modal, () => {
|
||||
expect(modal.querySelector("player-game-history-view")).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
modal.close();
|
||||
modal.remove();
|
||||
history.replaceState(null, "", "/");
|
||||
});
|
||||
|
||||
it("drills into Stats and returns to the cached Games scroll position", async () => {
|
||||
const shell = modal.querySelector("o-modal") as ModalShell;
|
||||
shell.setScrollTop(420);
|
||||
|
||||
const statsButton = Array.from(modal.querySelectorAll("button")).find(
|
||||
(button) => button.textContent?.trim() === "game_list.stats",
|
||||
);
|
||||
expect(statsButton).toBeTruthy();
|
||||
statsButton!.click();
|
||||
await waitForModal(modal, () => {
|
||||
expect(modal.querySelector("game-info-view")).not.toBeNull();
|
||||
});
|
||||
|
||||
expect(modal.textContent).toContain("game_list.stats");
|
||||
const statsView = modal.querySelector("game-info-view") as HTMLElement & {
|
||||
gameId: string;
|
||||
};
|
||||
expect(statsView.gameId).toBe("game-1");
|
||||
expect(shell.shadowRoot?.querySelector('[role="tablist"]')).toBeNull();
|
||||
expect(shell.getScrollTop()).toBe(0);
|
||||
expect(window.location.hash).toBe("#modal=account&gameID=game-1");
|
||||
|
||||
const backButton = modal.querySelector(
|
||||
'[slot="header"] button',
|
||||
) as HTMLButtonElement;
|
||||
backButton.click();
|
||||
await waitForModal(modal, () => {
|
||||
expect(modal.querySelector("player-game-history-view")).not.toBeNull();
|
||||
expect(shell.getScrollTop()).toBe(420);
|
||||
});
|
||||
expect(window.location.hash).toBe("#modal=account&tab=games");
|
||||
|
||||
expect(fetchPublicPlayerGames).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("opens a gameID route directly and returns to Games", async () => {
|
||||
modal.setActiveTab("account");
|
||||
modal.close();
|
||||
modal.open({ gameID: "game-1" });
|
||||
|
||||
await waitForModal(modal, () => {
|
||||
const statsView = modal.querySelector("game-info-view") as
|
||||
| (HTMLElement & { gameId: string })
|
||||
| null;
|
||||
expect(statsView?.gameId).toBe("game-1");
|
||||
});
|
||||
expect(window.location.hash).toBe("#modal=account&gameID=game-1");
|
||||
|
||||
const backButton = modal.querySelector(
|
||||
'[slot="header"] button',
|
||||
) as HTMLButtonElement;
|
||||
backButton.click();
|
||||
|
||||
await waitForModal(modal, () => {
|
||||
expect(modal.querySelector("player-game-history-view")).not.toBeNull();
|
||||
});
|
||||
expect(window.location.hash).toBe("#modal=account&tab=games");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user