mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-23 13:20:46 +00:00
## Description: <img width="930" height="755" alt="image" src="https://github.com/user-attachments/assets/f606bdce-fbeb-4fc3-98fb-ba6622634350" /> <img width="875" height="368" alt="image" src="https://github.com/user-attachments/assets/81c8243d-5dc0-4687-bed4-cadb6bafb808" /> ## 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
192 lines
5.9 KiB
TypeScript
192 lines
5.9 KiB
TypeScript
import {
|
|
afterAll,
|
|
afterEach,
|
|
beforeAll,
|
|
beforeEach,
|
|
describe,
|
|
expect,
|
|
it,
|
|
vi,
|
|
} from "vitest";
|
|
|
|
const copyToClipboardMock = vi.hoisted(() =>
|
|
vi.fn(async (_text: string, onSuccess?: () => void) => onSuccess?.()),
|
|
);
|
|
|
|
const fetchPublicPlayerProfileMock = vi.hoisted(() => vi.fn());
|
|
|
|
vi.mock("../../src/client/Utils", () => ({
|
|
translateText: vi.fn((key: string) => key),
|
|
copyToClipboard: copyToClipboardMock,
|
|
}));
|
|
|
|
vi.mock("../../src/client/Api", () => ({
|
|
fetchPublicPlayerProfile: fetchPublicPlayerProfileMock,
|
|
}));
|
|
|
|
vi.mock(
|
|
"../../src/client/components/baseComponents/stats/PlayerStatsTree",
|
|
() => {
|
|
class FakePlayerStatsTreeView extends HTMLElement {}
|
|
if (!customElements.get("player-stats-tree-view")) {
|
|
customElements.define("player-stats-tree-view", FakePlayerStatsTreeView);
|
|
}
|
|
return { PlayerStatsTreeView: FakePlayerStatsTreeView };
|
|
},
|
|
);
|
|
|
|
import type { CopyButton } from "../../src/client/components/CopyButton";
|
|
import { modalRouter } from "../../src/client/ModalRouter";
|
|
import { initNavigation } from "../../src/client/Navigation";
|
|
import {
|
|
PlayerProfileModal,
|
|
playerProfileUrl,
|
|
} from "../../src/client/PlayerProfileModal";
|
|
|
|
type ModalShell = HTMLElement & { updateComplete: Promise<boolean> };
|
|
|
|
const statsTree = { Public: { "Free For All": {} } };
|
|
|
|
describe("public player profile route", () => {
|
|
let modal: PlayerProfileModal;
|
|
let playPage: HTMLElement;
|
|
|
|
beforeAll(() => {
|
|
playPage = document.createElement("div");
|
|
playPage.id = "page-play";
|
|
document.body.appendChild(playPage);
|
|
initNavigation();
|
|
});
|
|
|
|
afterAll(() => {
|
|
playPage.remove();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
copyToClipboardMock.mockClear();
|
|
fetchPublicPlayerProfileMock.mockReset();
|
|
vi.stubGlobal("localStorage", { getItem: vi.fn(() => null) });
|
|
history.replaceState(null, "", "/");
|
|
modalRouter.register("profile", {
|
|
tag: "player-profile-modal",
|
|
pageId: "page-profile",
|
|
});
|
|
if (!customElements.get("player-profile-modal")) {
|
|
customElements.define("player-profile-modal", PlayerProfileModal);
|
|
}
|
|
|
|
modal = document.createElement(
|
|
"player-profile-modal",
|
|
) as PlayerProfileModal;
|
|
modal.id = "page-profile";
|
|
modal.setAttribute("inline", "");
|
|
modal.className = "hidden page-content";
|
|
document.body.appendChild(modal);
|
|
await modal.updateComplete;
|
|
});
|
|
|
|
afterEach(() => {
|
|
window.showPage?.("page-play");
|
|
modal.remove();
|
|
history.replaceState(null, "", "/");
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
it("opens a shared publicID and renders the fetched stats tree", async () => {
|
|
fetchPublicPlayerProfileMock.mockResolvedValue({
|
|
createdAt: "2026-01-01T00:00:00.000Z",
|
|
stats: statsTree,
|
|
});
|
|
history.replaceState(null, "", "/#modal=profile&publicID=shared-player");
|
|
|
|
expect(modalRouter.routeFromHash()).toBe(true);
|
|
|
|
await vi.waitFor(async () => {
|
|
await modal.updateComplete;
|
|
const shell = modal.querySelector("o-modal") as ModalShell | null;
|
|
await shell?.updateComplete;
|
|
const treeView = modal.querySelector("player-stats-tree-view") as
|
|
| (HTMLElement & { statsTree?: unknown })
|
|
| null;
|
|
expect(modal.isOpen()).toBe(true);
|
|
expect(treeView?.statsTree).toEqual(statsTree);
|
|
});
|
|
|
|
expect(fetchPublicPlayerProfileMock).toHaveBeenCalledWith("shared-player");
|
|
|
|
const copyButton = modal.querySelector<CopyButton>("copy-button")!;
|
|
await copyButton.updateComplete;
|
|
expect(copyButton.copyText).toBe(playerProfileUrl("shared-player"));
|
|
expect(copyButton.displayText).toBe("shared-player");
|
|
expect(copyButton.compact).toBe(true);
|
|
|
|
expect(window.location.hash).toBe("#modal=profile&publicID=shared-player");
|
|
|
|
const backButton = modal.querySelector(
|
|
'[slot="header"] button',
|
|
) as HTMLButtonElement;
|
|
backButton.click();
|
|
|
|
expect(modal.isOpen()).toBe(false);
|
|
expect(window.location.hash).toBe("");
|
|
});
|
|
|
|
it("shows not-found when the profile fetch fails", async () => {
|
|
fetchPublicPlayerProfileMock.mockResolvedValue(false);
|
|
history.replaceState(null, "", "/#modal=profile&publicID=missing");
|
|
|
|
expect(modalRouter.routeFromHash()).toBe(true);
|
|
|
|
await vi.waitFor(async () => {
|
|
await modal.updateComplete;
|
|
expect(modal.isOpen()).toBe(true);
|
|
expect(modal.querySelector("player-stats-tree-view")).toBeNull();
|
|
expect(modal.textContent).toContain("player_profile.not_found");
|
|
});
|
|
expect(fetchPublicPlayerProfileMock).toHaveBeenCalledWith("missing");
|
|
});
|
|
|
|
it("shows not-found without fetching when publicID is missing", async () => {
|
|
history.replaceState(null, "", "/#modal=profile");
|
|
|
|
expect(modalRouter.routeFromHash()).toBe(true);
|
|
|
|
await vi.waitFor(async () => {
|
|
await modal.updateComplete;
|
|
expect(modal.isOpen()).toBe(true);
|
|
expect(modal.textContent).toContain("player_profile.not_found");
|
|
});
|
|
expect(fetchPublicPlayerProfileMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("hands back to the clan modal when opened from a clan", async () => {
|
|
fetchPublicPlayerProfileMock.mockResolvedValue({
|
|
createdAt: "2026-01-01T00:00:00.000Z",
|
|
stats: statsTree,
|
|
});
|
|
// The clan modal decides which tab to restore (Members vs Game History)
|
|
// via returnFromPlayerProfile — the profile modal just hands back to it.
|
|
const returnFromPlayerProfile = vi.fn();
|
|
const fakeClanModal = document.createElement(
|
|
"clan-modal",
|
|
) as HTMLElement & {
|
|
returnFromPlayerProfile: () => void;
|
|
};
|
|
fakeClanModal.returnFromPlayerProfile = returnFromPlayerProfile;
|
|
document.body.appendChild(fakeClanModal);
|
|
|
|
modal.openFromClan("clan-member");
|
|
await modal.updateComplete;
|
|
expect(modal.isOpen()).toBe(true);
|
|
|
|
const backButton = modal.querySelector(
|
|
'[slot="header"] button',
|
|
) as HTMLButtonElement;
|
|
backButton.click();
|
|
|
|
expect(modal.isOpen()).toBe(false);
|
|
expect(returnFromPlayerProfile).toHaveBeenCalled();
|
|
fakeClanModal.remove();
|
|
});
|
|
});
|