Files
OpenFrontIO/tests/client/TerritoryPatternsModal.test.ts
T
2026-05-27 00:17:25 +01:00

129 lines
3.9 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
// Keep translations deterministic in tests
vi.mock("../../src/client/Utils", () => ({
translateText: (k: string) => k,
getSvgAspectRatio: async () => 1,
}));
// Mock cosmetics fetch so we can deterministically render owned patterns.
const fetchCosmeticsMock = vi.fn();
const getPlayerCosmeticsMock = vi.fn();
const resolveCosmetics = vi.fn();
const resolvedToPlayerPatternMock = vi.fn();
vi.mock("../../src/client/Cosmetics", () => ({
fetchCosmetics: (...args: any[]) => fetchCosmeticsMock(...args),
getPlayerCosmetics: (...args: any[]) => getPlayerCosmeticsMock(...args),
resolveCosmetics: (...args: any[]) => resolveCosmetics(...args),
resolvedToPlayerPattern: (...args: any[]) =>
resolvedToPlayerPatternMock(...args),
purchaseCosmetic: vi.fn(),
}));
// Stub CosmeticButton to avoid canvas rendering in JSDOM.
vi.mock("../../src/client/components/CosmeticButton", () => {
if (!customElements.get("cosmetic-button")) {
customElements.define(
"cosmetic-button",
class extends HTMLElement {
connectedCallback() {
this.innerHTML = '<button data-testid="cosmetic-btn">mock</button>';
}
},
);
}
return {};
});
import { TerritoryPatternsModal } from "../../src/client/TerritoryPatternsModal";
const makeUserMe = () =>
({
user: { discord: { id: "d" } },
player: { publicId: "client123", flares: [] },
}) as any;
const makeOwnedPattern = () =>
({
type: "pattern",
cosmetic: { name: "owned_pattern", pattern: "AQID" },
colorPalette: null,
relationship: "owned",
key: "pattern:owned_pattern",
}) as any;
describe("TerritoryPatternsModal", () => {
let modal: TerritoryPatternsModal;
beforeEach(async () => {
// Reset between tests: JSDOM may persist its real localStorage across
// tests, and the previous fallback closure persists its own `store`.
// Either way, clear before each run.
if (typeof (globalThis as any).localStorage?.clear === "function") {
(globalThis as any).localStorage.clear();
} else {
const store: Record<string, string> = {};
Object.defineProperty(globalThis, "localStorage", {
value: {
getItem: (k: string) => (k in store ? store[k] : null),
setItem: (k: string, v: string) => {
store[k] = String(v);
},
removeItem: (k: string) => {
delete store[k];
},
clear: () => {
for (const key of Object.keys(store)) delete store[key];
},
},
configurable: true,
});
}
if (!customElements.get("territory-patterns-modal")) {
customElements.define("territory-patterns-modal", TerritoryPatternsModal);
}
fetchCosmeticsMock.mockResolvedValue({
patterns: {},
colorPalettes: {},
});
getPlayerCosmeticsMock.mockResolvedValue({ pattern: null, color: null });
resolveCosmetics.mockReturnValue([makeOwnedPattern()]);
modal = document.createElement(
"territory-patterns-modal",
) as TerritoryPatternsModal;
modal.inline = true;
document.body.appendChild(modal);
await modal.updateComplete;
await modal.onUserMe(makeUserMe());
await modal.updateComplete;
});
afterEach(() => {
document.body.removeChild(modal);
vi.clearAllMocks();
});
it("renders owned patterns via cosmetic-button", async () => {
await modal.open();
await modal.updateComplete;
const buttons = modal.querySelectorAll("cosmetic-button");
expect(buttons.length).toBeGreaterThan(0);
});
it("shows the Store navigation button", async () => {
await modal.open();
await modal.updateComplete;
// The store button is rendered as an <o-button> custom element with translationKey="main.store"
const storeBtn = modal.querySelector(
'o-button[translationKey="main.store"]',
);
expect(storeBtn).toBeTruthy();
});
});