mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-18 17:53:29 +00:00
refactor: convert to npm-workspaces monorepo (engine/core-public/shared/client/server)
Restructure the single src/ tree into an npm-workspaces monorepo under
packages/, rename core -> engine, extract a types-only core-public layer,
and break the pre-existing engine -> client dependency cycle.
Structure (packages/):
core-public public API/wire schemas + shared enums (clean leaf)
shared framework-agnostic helpers (clean leaf)
engine deterministic simulation (was src/core)
client rendering/UI (was src/client)
server coordination (was src/server)
Dependency DAG: engine -> {core-public, shared}; client -> {core-public,
shared, engine}; server -> {core-public, engine}.
- npm workspaces: root package.json workspaces + per-package package.json;
tsconfig.base.json holds shared options + path aliases
(core-public/* shared/* engine/* client/* server/*) resolved uniformly by
tsc, Vite (resolve.tsconfigPaths), Vitest, and tsx. Lockfile regenerated.
- core-public: moved Schemas/ApiSchemas/CosmeticSchemas/StatsSchemas/
ClanApiSchemas/WorkerSchemas/Base64/PatternDecoder; extracted the enums
(GameTypes), GameEvent type, emoji table, and GraphicsOverrides schema.
Engine re-exports the moved enums/types so existing imports keep working.
- Broke engine -> client cycle:
- renderNumber/renderTroops -> shared/format
- NameBoxCalculator moved into engine
- username validation returns translation key + params; client translates
- applyStateUpdate moved to client (operates on the render-only PlayerState)
- Config/UnitGrid/execution-Util/GameImpl now use structural read
interfaces (engine/game/ReadViews: PlayerLike/UnitLike/GameLike) instead
of importing client view classes; client imports view classes from a new
client/view barrel; deleted the engine/game/GameView re-export shim.
- Build/deploy updated: vite.config, index.html, eslint, Dockerfile
(copies packages/ + tsconfig.base.json before npm ci), .vscode, tests.
Verified: tsc --noEmit clean; 1364 + 65 tests pass; production vite build
succeeds; engine has zero client/server imports; core-public and shared are
dependency leaves.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
cb9cab9aca
commit
a7f992e9b0
@@ -1,10 +1,10 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("../../../src/client/Api", () => ({
|
||||
vi.mock("client/Api", () => ({
|
||||
getApiBase: vi.fn(() => "http://localhost:3000"),
|
||||
}));
|
||||
|
||||
vi.mock("../../../src/client/Auth", () => ({
|
||||
vi.mock("client/Auth", () => ({
|
||||
getAuthHeader: vi.fn(async () => "Bearer test-token"),
|
||||
}));
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
banClanMember,
|
||||
fetchClanBans,
|
||||
unbanClanMember,
|
||||
} from "../../../src/client/ClanApi";
|
||||
} from "client/ClanApi";
|
||||
|
||||
const okJson = (data: unknown, status = 200) => ({
|
||||
ok: true,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("../../../src/client/Api", () => ({
|
||||
vi.mock("client/Api", () => ({
|
||||
getApiBase: vi.fn(() => "http://localhost:3000"),
|
||||
}));
|
||||
|
||||
vi.mock("../../../src/client/Auth", () => ({
|
||||
vi.mock("client/Auth", () => ({
|
||||
getAuthHeader: vi.fn(async () => "Bearer test-token"),
|
||||
}));
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
transferLeadership,
|
||||
updateClan,
|
||||
withdrawClanRequest,
|
||||
} from "../../../src/client/ClanApi";
|
||||
} from "client/ClanApi";
|
||||
|
||||
const okJson = (data: unknown, status = 200) => ({
|
||||
ok: true,
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("../../../src/client/Api", () => ({
|
||||
vi.mock("client/Api", () => ({
|
||||
getApiBase: vi.fn(() => "http://localhost:3000"),
|
||||
getUserMe: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../../src/client/Auth", () => ({
|
||||
vi.mock("client/Auth", () => ({
|
||||
getAuthHeader: vi.fn(async () => "Bearer test-token"),
|
||||
}));
|
||||
|
||||
import { getUserMe } from "../../../src/client/Api";
|
||||
import { getUserMe } from "client/Api";
|
||||
import {
|
||||
checkClanTagOwnership,
|
||||
fetchClanDetail,
|
||||
@@ -19,8 +19,8 @@ import {
|
||||
fetchClanMembers,
|
||||
fetchClanRequests,
|
||||
fetchClans,
|
||||
} from "../../../src/client/ClanApi";
|
||||
import type { UserMeResponse } from "../../../src/core/ApiSchemas";
|
||||
} from "client/ClanApi";
|
||||
import type { UserMeResponse } from "core-public/ApiSchemas";
|
||||
|
||||
const userWithClans = (tags: string[]): UserMeResponse =>
|
||||
({
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
ClanInfoSchema,
|
||||
ClanJoinRequestSchema,
|
||||
ClanMemberSchema,
|
||||
} from "../../../src/core/ClanApiSchemas";
|
||||
} from "core-public/ClanApiSchemas";
|
||||
|
||||
describe("ClanInfoSchema", () => {
|
||||
const base = {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { flushAsync } from "./ClanModalTestUtils";
|
||||
|
||||
// ─── Mocks (defined before imports so vi.mock hoisting applies) ─────────────
|
||||
|
||||
vi.mock("../../../src/client/Utils", () => ({
|
||||
vi.mock("client/Utils", () => ({
|
||||
// Echo the key so we can assert on translation slugs.
|
||||
translateText: vi.fn((key: string) => key),
|
||||
showToast: vi.fn(),
|
||||
@@ -13,22 +13,22 @@ vi.mock("../../../src/client/Utils", () => ({
|
||||
getMapName: vi.fn((m: string | undefined) => m ?? null),
|
||||
}));
|
||||
|
||||
vi.mock("../../../src/client/Auth", () => ({
|
||||
vi.mock("client/Auth", () => ({
|
||||
getAuthHeader: vi.fn(async () => "Bearer test-token"),
|
||||
userAuth: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../../src/client/ClanApi", () => ({
|
||||
vi.mock("client/ClanApi", () => ({
|
||||
fetchClanGames: vi.fn(async () => ({ results: [], nextCursor: null })),
|
||||
}));
|
||||
|
||||
vi.mock("../../../src/client/TerrainMapFileLoader", () => ({
|
||||
vi.mock("client/TerrainMapFileLoader", () => ({
|
||||
terrainMapFileLoader: {
|
||||
getMapData: vi.fn(() => ({ webpPath: "/maps/test.webp" })),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../../../src/client/ClientEnv", () => ({
|
||||
vi.mock("client/ClientEnv", () => ({
|
||||
ClientEnv: {
|
||||
workerPath: vi.fn(() => "w0"),
|
||||
},
|
||||
@@ -36,7 +36,7 @@ vi.mock("../../../src/client/ClientEnv", () => ({
|
||||
|
||||
// ClanShared re-exports from BaseModal; stub it directly so we don't pull
|
||||
// BaseModal's dependency graph into this unit test.
|
||||
vi.mock("../../../src/client/components/clan/ClanShared", async () => {
|
||||
vi.mock("client/components/clan/ClanShared", async () => {
|
||||
const { html } = await import("lit");
|
||||
return {
|
||||
renderLoadingSpinner: vi.fn(() => html`<div data-testid="spinner"></div>`),
|
||||
@@ -46,7 +46,7 @@ vi.mock("../../../src/client/components/clan/ClanShared", async () => {
|
||||
|
||||
// CopyButton is a custom element; stub it so its dependency graph (Auth,
|
||||
// API, etc.) doesn't get pulled in transitively.
|
||||
vi.mock("../../../src/client/components/CopyButton", () => ({}));
|
||||
vi.mock("client/components/CopyButton", () => ({}));
|
||||
|
||||
// jsdom doesn't ship IntersectionObserver — provide the minimum surface
|
||||
// the component touches (observe / disconnect). Tests below trigger the
|
||||
@@ -75,12 +75,12 @@ vi.stubGlobal("IntersectionObserver", FakeIntersectionObserver);
|
||||
|
||||
// ─── Imports under test ──────────────────────────────────────────────────────
|
||||
|
||||
import type { ClanGame, ClanGamesResponse } from "../../../src/client/ClanApi";
|
||||
import { fetchClanGames } from "../../../src/client/ClanApi";
|
||||
import type { ClanGame, ClanGamesResponse } from "client/ClanApi";
|
||||
import { fetchClanGames } from "client/ClanApi";
|
||||
import {
|
||||
ClanGameHistoryView,
|
||||
type ClanGameHistoryCache,
|
||||
} from "../../../src/client/components/clan/ClanGameHistoryView";
|
||||
} from "client/components/clan/ClanGameHistoryView";
|
||||
|
||||
// ─── Helpers ────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -17,16 +17,16 @@ import {
|
||||
} from "./ClanModalTestUtils";
|
||||
|
||||
vi.mock("@lit-labs/virtualizer/virtualize.js", () => virtualizerMockFactory());
|
||||
vi.mock("../../../src/client/Api", () => apiMockFactory());
|
||||
vi.mock("../../../src/client/ClanApi", () => clanApiMockFactory());
|
||||
vi.mock("../../../src/client/Utils", () => utilsMockFactory());
|
||||
vi.mock("../../../src/client/Auth", () => authMockFactory());
|
||||
vi.mock("../../../src/client/CrazyGamesSDK", () => crazyGamesSdkMockFactory());
|
||||
vi.mock("client/Api", () => apiMockFactory());
|
||||
vi.mock("client/ClanApi", () => clanApiMockFactory());
|
||||
vi.mock("client/Utils", () => utilsMockFactory());
|
||||
vi.mock("client/Auth", () => authMockFactory());
|
||||
vi.mock("client/CrazyGamesSDK", () => crazyGamesSdkMockFactory());
|
||||
|
||||
stubLocalStorage();
|
||||
|
||||
import type { ClanInfo } from "../../../src/client/ClanApi";
|
||||
import { ClanModal } from "../../../src/client/ClanModal";
|
||||
import type { ClanInfo } from "client/ClanApi";
|
||||
import { ClanModal } from "client/ClanModal";
|
||||
|
||||
describe("ClanModal — handlers", () => {
|
||||
let modal: ClanModal;
|
||||
@@ -50,7 +50,7 @@ describe("ClanModal — handlers", () => {
|
||||
describe("handleApprove increments selectedClan.memberCount", () => {
|
||||
it("increments memberCount by 1 after successful approveClanRequest", async () => {
|
||||
const { approveClanRequest, fetchClanRequests } =
|
||||
await import("../../../src/client/ClanApi");
|
||||
await import("client/ClanApi");
|
||||
(approveClanRequest as ReturnType<typeof vi.fn>).mockResolvedValue(true);
|
||||
(fetchClanRequests as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
results: [
|
||||
@@ -86,7 +86,7 @@ describe("ClanModal — handlers", () => {
|
||||
|
||||
it("does not increment memberCount when approveClanRequest fails", async () => {
|
||||
const { approveClanRequest, fetchClanRequests } =
|
||||
await import("../../../src/client/ClanApi");
|
||||
await import("client/ClanApi");
|
||||
(approveClanRequest as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
error: "clan_modal.error_generic",
|
||||
});
|
||||
@@ -120,7 +120,7 @@ describe("ClanModal — handlers", () => {
|
||||
|
||||
it("treats undefined memberCount as 0 and increments to 1", async () => {
|
||||
const { approveClanRequest, fetchClanRequests } =
|
||||
await import("../../../src/client/ClanApi");
|
||||
await import("client/ClanApi");
|
||||
(approveClanRequest as ReturnType<typeof vi.fn>).mockResolvedValue(true);
|
||||
(fetchClanRequests as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
results: [
|
||||
@@ -154,7 +154,7 @@ describe("ClanModal — handlers", () => {
|
||||
let manageView: Element;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { fetchClanMembers } = await import("../../../src/client/ClanApi");
|
||||
const { fetchClanMembers } = await import("client/ClanApi");
|
||||
(fetchClanMembers as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
results: [
|
||||
{
|
||||
@@ -188,7 +188,7 @@ describe("ClanModal — handlers", () => {
|
||||
});
|
||||
|
||||
it("handleBan calls banClanMember after confirm-dialog confirm", async () => {
|
||||
const { banClanMember } = await import("../../../src/client/ClanApi");
|
||||
const { banClanMember } = await import("client/ClanApi");
|
||||
(banClanMember as ReturnType<typeof vi.fn>).mockResolvedValue(true);
|
||||
|
||||
// Step 1: Click Ban button to open confirm dialog
|
||||
@@ -216,7 +216,7 @@ describe("ClanModal — handlers", () => {
|
||||
});
|
||||
|
||||
it("handleBan aborts when confirm-dialog cancel is clicked", async () => {
|
||||
const { banClanMember } = await import("../../../src/client/ClanApi");
|
||||
const { banClanMember } = await import("client/ClanApi");
|
||||
|
||||
// Step 1: Click Ban button to open confirm dialog
|
||||
const banButton = Array.from(modal.querySelectorAll("button")).find(
|
||||
@@ -237,7 +237,7 @@ describe("ClanModal — handlers", () => {
|
||||
});
|
||||
|
||||
it("handleBan sends undefined reason when confirm text is empty", async () => {
|
||||
const { banClanMember } = await import("../../../src/client/ClanApi");
|
||||
const { banClanMember } = await import("client/ClanApi");
|
||||
(banClanMember as ReturnType<typeof vi.fn>).mockResolvedValue(true);
|
||||
|
||||
// Step 1: Click Ban button
|
||||
@@ -265,7 +265,7 @@ describe("ClanModal — handlers", () => {
|
||||
|
||||
it("handleBan syncs memberCount via clan-updated event on success", async () => {
|
||||
const { banClanMember, fetchClanMembers } =
|
||||
await import("../../../src/client/ClanApi");
|
||||
await import("client/ClanApi");
|
||||
(banClanMember as ReturnType<typeof vi.fn>).mockResolvedValue(true);
|
||||
// Server returns the post-ban member total (was 5, now 4).
|
||||
(fetchClanMembers as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
@@ -303,7 +303,7 @@ describe("ClanModal — handlers", () => {
|
||||
describe("handleUnban", () => {
|
||||
it("removes ban from list and decrements total on success", async () => {
|
||||
const { unbanClanMember, fetchClanBans } =
|
||||
await import("../../../src/client/ClanApi");
|
||||
await import("client/ClanApi");
|
||||
(unbanClanMember as ReturnType<typeof vi.fn>).mockResolvedValue(true);
|
||||
(fetchClanBans as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
results: [
|
||||
@@ -341,7 +341,7 @@ describe("ClanModal — handlers", () => {
|
||||
let manageView: Element;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { fetchClanMembers } = await import("../../../src/client/ClanApi");
|
||||
const { fetchClanMembers } = await import("client/ClanApi");
|
||||
(fetchClanMembers as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
results: [
|
||||
{
|
||||
@@ -369,7 +369,7 @@ describe("ClanModal — handlers", () => {
|
||||
|
||||
it("calls kickMember and syncs memberCount on success", async () => {
|
||||
const { kickMember, fetchClanMembers } =
|
||||
await import("../../../src/client/ClanApi");
|
||||
await import("client/ClanApi");
|
||||
(kickMember as ReturnType<typeof vi.fn>).mockResolvedValue(true);
|
||||
(fetchClanMembers as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
results: [],
|
||||
@@ -402,7 +402,7 @@ describe("ClanModal — handlers", () => {
|
||||
|
||||
it("does not mutate state when kickMember fails", async () => {
|
||||
const { kickMember, fetchClanMembers } =
|
||||
await import("../../../src/client/ClanApi");
|
||||
await import("client/ClanApi");
|
||||
(kickMember as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
error: "clan_modal.error_generic",
|
||||
});
|
||||
@@ -436,7 +436,7 @@ describe("ClanModal — handlers", () => {
|
||||
let manageView: Element;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { fetchClanMembers } = await import("../../../src/client/ClanApi");
|
||||
const { fetchClanMembers } = await import("client/ClanApi");
|
||||
(fetchClanMembers as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
results: [],
|
||||
total: 3,
|
||||
@@ -467,7 +467,7 @@ describe("ClanModal — handlers", () => {
|
||||
});
|
||||
|
||||
it("calls disbandClan, clears selection, and returns to list on success", async () => {
|
||||
const { disbandClan } = await import("../../../src/client/ClanApi");
|
||||
const { disbandClan } = await import("client/ClanApi");
|
||||
(disbandClan as ReturnType<typeof vi.fn>).mockResolvedValue(true);
|
||||
|
||||
// Open the disband confirm dialog on the manage view.
|
||||
@@ -495,7 +495,7 @@ describe("ClanModal — handlers", () => {
|
||||
});
|
||||
|
||||
it("preserves selection when disbandClan fails", async () => {
|
||||
const { disbandClan } = await import("../../../src/client/ClanApi");
|
||||
const { disbandClan } = await import("client/ClanApi");
|
||||
(disbandClan as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
error: "clan_modal.error_generic",
|
||||
});
|
||||
@@ -525,7 +525,7 @@ describe("ClanModal — handlers", () => {
|
||||
let requestsView: Element;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { fetchClanRequests } = await import("../../../src/client/ClanApi");
|
||||
const { fetchClanRequests } = await import("client/ClanApi");
|
||||
(fetchClanRequests as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
results: [
|
||||
{ publicId: "applicant-1", createdAt: "2024-06-01T00:00:00Z" },
|
||||
@@ -543,7 +543,7 @@ describe("ClanModal — handlers", () => {
|
||||
});
|
||||
|
||||
it("removes the request and decrements totals on success", async () => {
|
||||
const { denyClanRequest } = await import("../../../src/client/ClanApi");
|
||||
const { denyClanRequest } = await import("client/ClanApi");
|
||||
(denyClanRequest as ReturnType<typeof vi.fn>).mockResolvedValue(true);
|
||||
|
||||
const denyButton = Array.from(modal.querySelectorAll("button")).find(
|
||||
@@ -564,7 +564,7 @@ describe("ClanModal — handlers", () => {
|
||||
});
|
||||
|
||||
it("does not mutate state when denyClanRequest fails", async () => {
|
||||
const { denyClanRequest } = await import("../../../src/client/ClanApi");
|
||||
const { denyClanRequest } = await import("client/ClanApi");
|
||||
(denyClanRequest as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
error: "clan_modal.error_generic",
|
||||
});
|
||||
@@ -589,7 +589,7 @@ describe("ClanModal — handlers", () => {
|
||||
|
||||
describe("handleJoin", () => {
|
||||
beforeEach(async () => {
|
||||
const { fetchClanDetail } = await import("../../../src/client/ClanApi");
|
||||
const { fetchClanDetail } = await import("client/ClanApi");
|
||||
(fetchClanDetail as ReturnType<typeof vi.fn>).mockResolvedValueOnce(
|
||||
makeClan({ isOpen: true, memberCount: 5 }),
|
||||
);
|
||||
@@ -602,7 +602,7 @@ describe("ClanModal — handlers", () => {
|
||||
|
||||
it("switches detail view into member mode immediately after open-clan join", async () => {
|
||||
const { joinClan, fetchClanMembers } =
|
||||
await import("../../../src/client/ClanApi");
|
||||
await import("client/ClanApi");
|
||||
(joinClan as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
status: "joined",
|
||||
});
|
||||
@@ -651,7 +651,7 @@ describe("ClanModal — handlers", () => {
|
||||
describe("handleLeave", () => {
|
||||
beforeEach(async () => {
|
||||
const { fetchClanDetail, fetchClanMembers } =
|
||||
await import("../../../src/client/ClanApi");
|
||||
await import("client/ClanApi");
|
||||
(fetchClanDetail as ReturnType<typeof vi.fn>).mockResolvedValueOnce(
|
||||
makeClan(),
|
||||
);
|
||||
@@ -680,7 +680,7 @@ describe("ClanModal — handlers", () => {
|
||||
});
|
||||
|
||||
it("calls leaveClan, removes role, and returns to list on success", async () => {
|
||||
const { leaveClan } = await import("../../../src/client/ClanApi");
|
||||
const { leaveClan } = await import("client/ClanApi");
|
||||
(leaveClan as ReturnType<typeof vi.fn>).mockResolvedValue(true);
|
||||
|
||||
const leaveButton = Array.from(modal.querySelectorAll("button")).find(
|
||||
@@ -704,7 +704,7 @@ describe("ClanModal — handlers", () => {
|
||||
});
|
||||
|
||||
it("preserves selection when leaveClan fails", async () => {
|
||||
const { leaveClan } = await import("../../../src/client/ClanApi");
|
||||
const { leaveClan } = await import("client/ClanApi");
|
||||
(leaveClan as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
error: "clan_modal.error_generic",
|
||||
});
|
||||
@@ -732,7 +732,7 @@ describe("ClanModal — handlers", () => {
|
||||
let transferView: Element;
|
||||
|
||||
beforeEach(async () => {
|
||||
const { fetchClanMembers } = await import("../../../src/client/ClanApi");
|
||||
const { fetchClanMembers } = await import("client/ClanApi");
|
||||
(fetchClanMembers as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
results: [
|
||||
{
|
||||
@@ -766,7 +766,7 @@ describe("ClanModal — handlers", () => {
|
||||
|
||||
it("clears confirmAction and removes the dialog after confirming", async () => {
|
||||
const { transferLeadership } =
|
||||
await import("../../../src/client/ClanApi");
|
||||
await import("client/ClanApi");
|
||||
(transferLeadership as ReturnType<typeof vi.fn>).mockResolvedValue(true);
|
||||
|
||||
const dialog = modal.querySelector("confirm-dialog");
|
||||
@@ -786,7 +786,7 @@ describe("ClanModal — handlers", () => {
|
||||
|
||||
it("clears confirmAction when cancel is clicked, without calling the API", async () => {
|
||||
const { transferLeadership } =
|
||||
await import("../../../src/client/ClanApi");
|
||||
await import("client/ClanApi");
|
||||
|
||||
const dialog = modal.querySelector("confirm-dialog");
|
||||
expect(dialog).toBeTruthy();
|
||||
|
||||
@@ -15,15 +15,15 @@ import {
|
||||
} from "./ClanModalTestUtils";
|
||||
|
||||
vi.mock("@lit-labs/virtualizer/virtualize.js", () => virtualizerMockFactory());
|
||||
vi.mock("../../../src/client/Api", () => apiMockFactory());
|
||||
vi.mock("../../../src/client/ClanApi", () => clanApiMockFactory());
|
||||
vi.mock("../../../src/client/Utils", () => utilsMockFactory());
|
||||
vi.mock("../../../src/client/Auth", () => authMockFactory());
|
||||
vi.mock("../../../src/client/CrazyGamesSDK", () => crazyGamesSdkMockFactory());
|
||||
vi.mock("client/Api", () => apiMockFactory());
|
||||
vi.mock("client/ClanApi", () => clanApiMockFactory());
|
||||
vi.mock("client/Utils", () => utilsMockFactory());
|
||||
vi.mock("client/Auth", () => authMockFactory());
|
||||
vi.mock("client/CrazyGamesSDK", () => crazyGamesSdkMockFactory());
|
||||
|
||||
stubLocalStorage();
|
||||
|
||||
import { ClanModal } from "../../../src/client/ClanModal";
|
||||
import { ClanModal } from "client/ClanModal";
|
||||
|
||||
describe("ClanModal — rendering", () => {
|
||||
let modal: ClanModal;
|
||||
@@ -51,7 +51,7 @@ describe("ClanModal — rendering", () => {
|
||||
// Directly invoke renderClanCard via the instance and insert the result
|
||||
// into a container so we can query it. We do this by populating myClans
|
||||
// and myClanRoles state so the list view renders real cards.
|
||||
const { getUserMe } = await import("../../../src/client/Api");
|
||||
const { getUserMe } = await import("client/Api");
|
||||
(getUserMe as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
player: {
|
||||
publicId: "test-player",
|
||||
@@ -88,7 +88,7 @@ describe("ClanModal — rendering", () => {
|
||||
});
|
||||
|
||||
it("shows 'clan_modal.open' badge when clan is open and user has no role", async () => {
|
||||
const { fetchClans } = await import("../../../src/client/ClanApi");
|
||||
const { fetchClans } = await import("client/ClanApi");
|
||||
(fetchClans as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
results: [makeClan({ tag: "OTH", name: "Other Clan", isOpen: true })],
|
||||
total: 1,
|
||||
@@ -107,7 +107,7 @@ describe("ClanModal — rendering", () => {
|
||||
});
|
||||
|
||||
it("shows 'clan_modal.invite_only' badge when clan is closed and user has no role", async () => {
|
||||
const { fetchClans } = await import("../../../src/client/ClanApi");
|
||||
const { fetchClans } = await import("client/ClanApi");
|
||||
(fetchClans as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
results: [makeClan({ tag: "INV", name: "Invite Clan", isOpen: false })],
|
||||
total: 1,
|
||||
@@ -187,7 +187,7 @@ describe("ClanModal — rendering", () => {
|
||||
});
|
||||
|
||||
it("does NOT show a role badge when myClanRoles has no entry for the clan", async () => {
|
||||
const { fetchClans } = await import("../../../src/client/ClanApi");
|
||||
const { fetchClans } = await import("client/ClanApi");
|
||||
(fetchClans as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
results: [makeClan({ tag: "INV", name: "Invite Clan", isOpen: false })],
|
||||
total: 1,
|
||||
@@ -227,7 +227,7 @@ describe("ClanModal — rendering", () => {
|
||||
expect(modal.textContent).not.toContain("undefined");
|
||||
// translateText mock swallows args and returns the key, so verify it
|
||||
// was called with count: 0 (the fallback) rather than count: undefined.
|
||||
const { translateText } = await import("../../../src/client/Utils");
|
||||
const { translateText } = await import("client/Utils");
|
||||
const calls = (translateText as ReturnType<typeof vi.fn>).mock.calls;
|
||||
const memberCountCall = calls.find(
|
||||
(c) => c[0] === "clan_modal.member_count",
|
||||
@@ -237,7 +237,7 @@ describe("ClanModal — rendering", () => {
|
||||
});
|
||||
|
||||
it("shows 0 in the stats row of the detail view when memberCount is undefined", async () => {
|
||||
const { fetchClanDetail } = await import("../../../src/client/ClanApi");
|
||||
const { fetchClanDetail } = await import("client/ClanApi");
|
||||
(fetchClanDetail as ReturnType<typeof vi.fn>).mockResolvedValueOnce(
|
||||
makeClan({ memberCount: undefined }),
|
||||
);
|
||||
@@ -251,7 +251,7 @@ describe("ClanModal — rendering", () => {
|
||||
});
|
||||
|
||||
it("shows 0 in the manage members header when memberCount is undefined", async () => {
|
||||
const { fetchClanMembers } = await import("../../../src/client/ClanApi");
|
||||
const { fetchClanMembers } = await import("client/ClanApi");
|
||||
(fetchClanMembers as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
results: [],
|
||||
total: 0,
|
||||
@@ -277,7 +277,7 @@ describe("ClanModal — rendering", () => {
|
||||
|
||||
describe("Open/Closed toggle ARIA attributes in manage view", () => {
|
||||
beforeEach(async () => {
|
||||
const { fetchClanMembers } = await import("../../../src/client/ClanApi");
|
||||
const { fetchClanMembers } = await import("client/ClanApi");
|
||||
(fetchClanMembers as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
results: [],
|
||||
total: 0,
|
||||
@@ -355,7 +355,7 @@ describe("ClanModal — rendering", () => {
|
||||
|
||||
describe("Ban feature — bans view", () => {
|
||||
it("renders Banned Players button in manage view", async () => {
|
||||
const { fetchClanMembers } = await import("../../../src/client/ClanApi");
|
||||
const { fetchClanMembers } = await import("client/ClanApi");
|
||||
(fetchClanMembers as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
results: [],
|
||||
total: 0,
|
||||
@@ -374,7 +374,7 @@ describe("ClanModal — rendering", () => {
|
||||
});
|
||||
|
||||
it("renders ban list with unban button in bans view", async () => {
|
||||
const { fetchClanBans } = await import("../../../src/client/ClanApi");
|
||||
const { fetchClanBans } = await import("client/ClanApi");
|
||||
(fetchClanBans as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
results: [
|
||||
{
|
||||
@@ -400,7 +400,7 @@ describe("ClanModal — rendering", () => {
|
||||
});
|
||||
|
||||
it("renders empty state when no bans", async () => {
|
||||
const { fetchClanBans } = await import("../../../src/client/ClanApi");
|
||||
const { fetchClanBans } = await import("client/ClanApi");
|
||||
(fetchClanBans as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
||||
results: [],
|
||||
total: 0,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { vi } from "vitest";
|
||||
import type { ClanInfo } from "../../../src/client/ClanApi";
|
||||
import type { ClanModal } from "../../../src/client/ClanModal";
|
||||
import type { ClanInfo } from "client/ClanApi";
|
||||
import type { ClanModal } from "client/ClanModal";
|
||||
|
||||
// ─── Mock factories ─────────────────────────────────────────────────────────
|
||||
// Each factory returns a fresh object of vi.fn()s. Test files pass these to
|
||||
|
||||
@@ -4,12 +4,12 @@ import type {
|
||||
ClanJoinRequest,
|
||||
ClanMember,
|
||||
ClanMemberStats,
|
||||
} from "../../../src/client/ClanApi";
|
||||
} from "client/ClanApi";
|
||||
import {
|
||||
filterMembersBySearch,
|
||||
filterRequestsBySearch,
|
||||
renderMemberStats,
|
||||
} from "../../../src/client/components/clan/ClanShared";
|
||||
} from "client/components/clan/ClanShared";
|
||||
|
||||
const members: ClanMember[] = [
|
||||
{ publicId: "Alice123", role: "leader", joinedAt: "2024-01-01T00:00:00Z" },
|
||||
|
||||
Reference in New Issue
Block a user