183 lines
7.1 KiB
TypeScript
183 lines
7.1 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import {
|
|
BITE_SIZED_STOCKADES_DUNGEON_ID,
|
|
RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID,
|
|
} from "../game/dungeonContentSource";
|
|
import type { DungeonRunProgress } from "../game/dungeonRunTypes";
|
|
import { DEFAULT_DUNGEON_QUEUE_POLICY } from "../game/dungeonQueuePolicy";
|
|
import { canStartAnotherBiteSizedDungeon, startAnotherBiteSizedDungeon } from "./biteSizedDungeonRuntime";
|
|
import { useOnlineGroupStore } from "./onlineGroupStore";
|
|
import { useOnlineSessionStore } from "./onlineSessionStore";
|
|
import { useShellStore } from "./shellStore";
|
|
|
|
const progress: DungeonRunProgress = {
|
|
id: "completed-bite-sized-run",
|
|
dungeonId: BITE_SIZED_STOCKADES_DUNGEON_ID,
|
|
difficultyId: "normal",
|
|
bossDifficulty: 1,
|
|
startedAt: 1_000,
|
|
finishedAt: 9_000,
|
|
durationMs: 8_000,
|
|
timed: true,
|
|
killedBossIds: ["creature-84027"],
|
|
party: [{ name: "Tester", classId: "priest", level: 17, role: "healer", npc: false }],
|
|
};
|
|
|
|
const originalShell = useShellStore.getState();
|
|
const originalGroup = useOnlineGroupStore.getState();
|
|
const originalOnline = useOnlineSessionStore.getState();
|
|
|
|
beforeEach(() => {
|
|
useOnlineGroupStore.getState().reset();
|
|
useOnlineSessionStore.getState().reset();
|
|
});
|
|
|
|
afterEach(() => {
|
|
useShellStore.setState(originalShell, true);
|
|
useOnlineGroupStore.setState(originalGroup, true);
|
|
useOnlineSessionStore.setState(originalOnline, true);
|
|
});
|
|
|
|
describe("bite-sized replay", () => {
|
|
it("does not replay a disabled randomized location", async () => {
|
|
const enterDungeon = vi.fn(() => true);
|
|
useShellStore.setState({
|
|
session: { kind: "offline", ownerId: "local-owner", displayName: "Tester" },
|
|
activeDungeonRole: "healer",
|
|
enterDungeon,
|
|
setNotice: vi.fn(),
|
|
});
|
|
await expect(startAnotherBiteSizedDungeon(
|
|
{ ...progress, dungeonId: RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID },
|
|
{
|
|
random: () => 0,
|
|
loadPolicy: async () => ({ schemaVersion: 1, disabled: {
|
|
regular: [], biteSized: [], randomizedBiteSized: [RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID],
|
|
} }),
|
|
createRunId: () => "unused",
|
|
contentIsUsable: () => true,
|
|
prepareRandomizedContent: vi.fn(),
|
|
},
|
|
)).resolves.toBe(false);
|
|
expect(enterDungeon).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("starts a fresh local run and treats the local player as party leader", async () => {
|
|
const enterDungeon = vi.fn(() => true);
|
|
const saveCheckpoint = vi.fn();
|
|
useShellStore.setState({
|
|
session: { kind: "offline", ownerId: "local-owner", displayName: "Tester" },
|
|
activeDungeonRole: "healer",
|
|
enterDungeon,
|
|
saveActiveDungeonRunCheckpoint: saveCheckpoint,
|
|
});
|
|
|
|
expect(canStartAnotherBiteSizedDungeon()).toBe(true);
|
|
await expect(startAnotherBiteSizedDungeon(progress)).resolves.toBe(true);
|
|
expect(saveCheckpoint).toHaveBeenCalledWith(null);
|
|
expect(enterDungeon).toHaveBeenCalledWith(expect.stringMatching(/^bite-sized-/), "healer", {
|
|
difficultyId: "normal",
|
|
bossDifficulty: 1,
|
|
freshRun: true,
|
|
runId: expect.any(String),
|
|
});
|
|
expect(enterDungeon).not.toHaveBeenCalledWith(BITE_SIZED_STOCKADES_DUNGEON_ID,
|
|
expect.anything(), expect.anything());
|
|
});
|
|
|
|
it("replays randomized mode with a new seed and prepares that seed's creature assets", async () => {
|
|
const enterDungeon = vi.fn(() => true);
|
|
const saveCheckpoint = vi.fn();
|
|
const prepareRandomizedContent = vi.fn(async () => undefined);
|
|
useShellStore.setState({
|
|
session: { kind: "offline", ownerId: "local-owner", displayName: "Tester" },
|
|
activeDungeonRole: "healer",
|
|
enterDungeon,
|
|
saveActiveDungeonRunCheckpoint: saveCheckpoint,
|
|
});
|
|
const randomizedProgress = { ...progress, dungeonId: RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID };
|
|
|
|
await expect(startAnotherBiteSizedDungeon(randomizedProgress, {
|
|
random: () => 0,
|
|
loadPolicy: async () => DEFAULT_DUNGEON_QUEUE_POLICY,
|
|
createRunId: () => "next-randomized-seed",
|
|
contentIsUsable: () => true,
|
|
prepareRandomizedContent,
|
|
})).resolves.toBe(true);
|
|
|
|
expect(prepareRandomizedContent).toHaveBeenCalledWith("next-randomized-seed", RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID);
|
|
expect(saveCheckpoint).toHaveBeenCalledWith(null);
|
|
expect(enterDungeon).toHaveBeenCalledWith(RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID, "healer", {
|
|
difficultyId: "normal",
|
|
bossDifficulty: 1,
|
|
freshRun: true,
|
|
runId: "next-randomized-seed",
|
|
});
|
|
});
|
|
|
|
it("lets only the online group leader publish the next shared activity", async () => {
|
|
const startActivity = vi.fn(async () => true);
|
|
const saveCheckpoint = vi.fn();
|
|
const activity = {
|
|
id: "shared-bite-sized-run",
|
|
type: "dungeon" as const,
|
|
contentId: BITE_SIZED_STOCKADES_DUNGEON_ID,
|
|
fillWithAi: true,
|
|
partySize: 4,
|
|
startingLevel: null,
|
|
startedAt: 1_000,
|
|
};
|
|
useShellStore.setState({
|
|
session: { kind: "account", ownerId: "leader", displayName: "Leader", accessToken: "test-token" },
|
|
saveActiveDungeonRunCheckpoint: saveCheckpoint,
|
|
});
|
|
useOnlineGroupStore.setState({
|
|
startActivity,
|
|
snapshot: { invitations: [], group: { id: "group", leaderAccountId: "leader", members: [], activity,
|
|
activityRevision: 1, createdAt: 1, updatedAt: 1 } },
|
|
});
|
|
|
|
expect(canStartAnotherBiteSizedDungeon()).toBe(true);
|
|
await expect(startAnotherBiteSizedDungeon(progress)).resolves.toBe(true);
|
|
expect(startActivity).toHaveBeenCalledWith(expect.objectContaining({ ownerId: "leader" }), {
|
|
type: "dungeon",
|
|
contentId: expect.stringMatching(/^bite-sized-/),
|
|
difficultyId: "normal",
|
|
bossDifficulty: 1,
|
|
partySize: 4,
|
|
});
|
|
expect(saveCheckpoint).toHaveBeenCalledWith(null);
|
|
|
|
useShellStore.setState({ session: { kind: "account", ownerId: "follower", displayName: "Follower", accessToken: "test-token" } });
|
|
expect(canStartAnotherBiteSizedDungeon()).toBe(false);
|
|
});
|
|
|
|
it("publishes another randomized activity instead of crossing an online group into regular Bite-Sized", async () => {
|
|
const startActivity = vi.fn(async () => true);
|
|
useShellStore.setState({
|
|
session: { kind: "account", ownerId: "leader", displayName: "Leader", accessToken: "test-token" },
|
|
saveActiveDungeonRunCheckpoint: vi.fn(),
|
|
});
|
|
useOnlineGroupStore.setState({
|
|
startActivity,
|
|
snapshot: { invitations: [], group: { id: "group", leaderAccountId: "leader", members: [], activity: null,
|
|
activityRevision: 2, createdAt: 1, updatedAt: 2 } },
|
|
});
|
|
|
|
await expect(startAnotherBiteSizedDungeon(
|
|
{ ...progress, dungeonId: RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID },
|
|
{ random: () => 0, createRunId: () => "unused-local-seed", contentIsUsable: () => true,
|
|
loadPolicy: async () => DEFAULT_DUNGEON_QUEUE_POLICY,
|
|
prepareRandomizedContent: vi.fn() },
|
|
)).resolves.toBe(true);
|
|
|
|
expect(startActivity).toHaveBeenCalledWith(expect.objectContaining({ ownerId: "leader" }), {
|
|
type: "dungeon",
|
|
contentId: RANDOMIZED_BITE_SIZED_STOCKADES_DUNGEON_ID,
|
|
difficultyId: "normal",
|
|
bossDifficulty: 1,
|
|
partySize: 5,
|
|
});
|
|
});
|
|
});
|