56 lines
3.6 KiB
TypeScript
56 lines
3.6 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { localDungeonLeaderboard, localDungeonRunPlacement, saveLocalDungeonRun, submitDungeonRun } from "./dungeonLeaderboardClient";
|
|
import type { DungeonRunRecord } from "../game/dungeonRunTypes";
|
|
import catalog from "../game/generated/dungeonLeaderboardCatalog.generated.json";
|
|
|
|
const dungeon = catalog.dungeons.find((entry) => entry.id === "deadmines")!;
|
|
const record = (id: string, durationMs: number, fullClear = true): DungeonRunRecord => ({
|
|
id, dungeonId: dungeon.id, difficultyId: dungeon.defaultDifficultyId, durationMs, finishedAt: 100000,
|
|
party: [{ name: "Tester", classId: "priest", level: 20, role: "healer", npc: false }],
|
|
killedBossIds: fullClear ? dungeon.bosses.map((boss) => boss.id) : [dungeon.finalBossId], fullClear,
|
|
});
|
|
beforeEach(() => {
|
|
const values = new Map<string, string>();
|
|
vi.stubGlobal("localStorage", { getItem: (key: string) => values.get(key) ?? null,
|
|
setItem: (key: string, value: string) => values.set(key, value) });
|
|
});
|
|
afterEach(() => vi.unstubAllGlobals());
|
|
|
|
describe("offline dungeon leaderboard", () => {
|
|
it("isolates all ten difficulty levels, ranks, pages, and legacy records", () => {
|
|
saveLocalDungeonRun(record("legacy-fastest", 1));
|
|
for (let difficulty = 1; difficulty <= 10; difficulty += 1) {
|
|
const run = { ...record(`slider-run-${difficulty}`, 10000 + difficulty), bossDifficulty: difficulty };
|
|
saveLocalDungeonRun(run);
|
|
expect(localDungeonLeaderboard(dungeon.id, dungeon.defaultDifficultyId, "final", 0, difficulty).entries
|
|
.map((entry) => entry.id)).toEqual([run.id]);
|
|
expect(localDungeonRunPlacement(run)).toEqual({ finalRank: 1, finalTotal: 1, fullRank: 1, fullTotal: 1 });
|
|
}
|
|
expect(localDungeonLeaderboard(dungeon.id, dungeon.defaultDifficultyId, "final").total).toBe(1);
|
|
});
|
|
it("persists full/final filters, ties, pages, and the placement of a run outside the first page", () => {
|
|
for (let index = 0; index < 25; index += 1) saveLocalDungeonRun(record(`offline-${index}`, 1000 + index * 100));
|
|
const fastest = record("offline-fastest", 500, false);
|
|
saveLocalDungeonRun(fastest); saveLocalDungeonRun(fastest);
|
|
saveLocalDungeonRun(record("offline-tie", 1000));
|
|
const all = localDungeonLeaderboard(dungeon.id, dungeon.defaultDifficultyId, "final");
|
|
expect(all.total).toBe(27); expect(all.entries[0].id).toBe(fastest.id);
|
|
expect(all.entries.slice(0, 3).map((entry) => entry.rank)).toEqual([1, 2, 2]);
|
|
expect(localDungeonLeaderboard(dungeon.id, dungeon.defaultDifficultyId, "full").total).toBe(26);
|
|
const page = localDungeonLeaderboard(dungeon.id, dungeon.defaultDifficultyId, "final", 20);
|
|
expect(page.entries).toHaveLength(7);
|
|
expect(localDungeonRunPlacement(record("offline-24", 3400))).toEqual({ finalRank: 27, finalTotal: 27, fullRank: 26, fullTotal: 26 });
|
|
});
|
|
it("retains the local time after a failed upload and uses the same run id when retried", async () => {
|
|
const run = record("offline-retry-test", 4321);
|
|
saveLocalDungeonRun(run);
|
|
const fetch = vi.fn().mockRejectedValueOnce(new Error("offline"))
|
|
.mockResolvedValueOnce(new Response(JSON.stringify({ placement: { finalRank: 2, finalTotal: 3, fullRank: 1, fullTotal: 2 } }), { status: 201 }));
|
|
vi.stubGlobal("fetch", fetch);
|
|
await expect(submitDungeonRun(run, null)).rejects.toThrow();
|
|
expect(localDungeonLeaderboard(dungeon.id, dungeon.defaultDifficultyId, "final").entries[0].durationMs).toBe(4321);
|
|
await expect(submitDungeonRun(run, null)).resolves.toMatchObject({ finalRank: 2 });
|
|
expect(fetch.mock.calls.map((call) => JSON.parse(call[1].body).id)).toEqual([run.id, run.id]);
|
|
});
|
|
});
|