137 lines
6.0 KiB
TypeScript
137 lines
6.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { DUNGEON_CATALOG } from "./dungeonCatalog";
|
|
import {
|
|
configWithGmReviewNote,
|
|
configWithGmReviewStatus,
|
|
describeGmReviewTarget,
|
|
drawRandomGmReviewTarget,
|
|
gmReviewQueue,
|
|
type GmReviewTarget,
|
|
} from "./gmContentReview";
|
|
import {
|
|
DEFAULT_MANASTORM_ADMIN_CONFIG,
|
|
resolvedBossAdminCatalog,
|
|
type GmReadinessStatus,
|
|
type ManastormAdminConfig,
|
|
} from "./manastormAdminConfig";
|
|
|
|
function configWithBossStatuses(statuses: Readonly<Record<string, GmReadinessStatus>>): ManastormAdminConfig {
|
|
return {
|
|
...DEFAULT_MANASTORM_ADMIN_CONFIG,
|
|
bosses: Object.fromEntries(Object.entries(statuses).map(([key, status]) => [key, { status }])),
|
|
};
|
|
}
|
|
|
|
describe("random GM content review", () => {
|
|
it("draws Needs tested dungeons before Not ready and never draws Ready", () => {
|
|
const available = DUNGEON_CATALOG.filter((dungeon) => dungeon.available);
|
|
expect(available.length).toBeGreaterThan(2);
|
|
const config: ManastormAdminConfig = {
|
|
...DEFAULT_MANASTORM_ADMIN_CONFIG,
|
|
dungeons: Object.fromEntries(available.map((dungeon, index) => [
|
|
dungeon.id,
|
|
{ status: index === 0 ? "needs-tested" : index === 1 ? "not-ready" : "ready" },
|
|
])),
|
|
};
|
|
|
|
expect(drawRandomGmReviewTarget("dungeon", config, null, () => 0.99)).toEqual({
|
|
kind: "dungeon",
|
|
dungeonId: available[0].id,
|
|
});
|
|
|
|
const withoutNeedsTested = configWithGmReviewStatus(
|
|
config,
|
|
{ kind: "dungeon", dungeonId: available[0].id },
|
|
"ready",
|
|
)!;
|
|
expect(drawRandomGmReviewTarget("dungeon", withoutNeedsTested, null, () => 0)).toEqual({
|
|
kind: "dungeon",
|
|
dungeonId: available[1].id,
|
|
});
|
|
});
|
|
|
|
it("uses separate dungeon-boss and imported-Manastorm source queues", () => {
|
|
const catalog = resolvedBossAdminCatalog(DEFAULT_MANASTORM_ADMIN_CONFIG);
|
|
const dungeonBosses = catalog.filter((entry) => entry.runnable && entry.source.includes("dungeon"));
|
|
const manastorms = catalog.filter((entry) => entry.runnable && entry.source.includes("installed-manastorm"));
|
|
expect(dungeonBosses.length).toBeGreaterThan(0);
|
|
expect(manastorms.length).toBeGreaterThan(0);
|
|
|
|
const bossQueue = gmReviewQueue("boss", DEFAULT_MANASTORM_ADMIN_CONFIG);
|
|
const manastormQueue = gmReviewQueue("manastorm", DEFAULT_MANASTORM_ADMIN_CONFIG);
|
|
expect(bossQueue.every((target) => target.kind === "boss"
|
|
&& dungeonBosses.some((entry) => entry.key === target.bossKey))).toBe(true);
|
|
expect(manastormQueue.every((target) => target.kind === "manastorm"
|
|
&& manastorms.some((entry) => entry.key === target.bossKey))).toBe(true);
|
|
|
|
const overlap = catalog.find((entry) => entry.runnable
|
|
&& entry.source.includes("dungeon")
|
|
&& entry.source.includes("installed-manastorm"));
|
|
expect(overlap).toBeTruthy();
|
|
expect(bossQueue).toContainEqual({ kind: "boss", bossKey: overlap!.key });
|
|
expect(manastormQueue).toContainEqual({ kind: "manastorm", bossKey: overlap!.key });
|
|
});
|
|
|
|
it("avoids an immediate repeat when another prioritized target exists", () => {
|
|
const targets = gmReviewQueue("manastorm", DEFAULT_MANASTORM_ADMIN_CONFIG);
|
|
expect(targets.length).toBeGreaterThan(1);
|
|
const previous = targets[0] as GmReviewTarget;
|
|
const next = drawRandomGmReviewTarget("manastorm", DEFAULT_MANASTORM_ADMIN_CONFIG, previous, () => 0);
|
|
expect(next).not.toEqual(previous);
|
|
});
|
|
|
|
it("returns no target when every runnable record is Ready", () => {
|
|
const catalog = resolvedBossAdminCatalog(DEFAULT_MANASTORM_ADMIN_CONFIG);
|
|
const config = configWithBossStatuses(Object.fromEntries(catalog.map((entry) => [entry.key, "ready"])));
|
|
expect(drawRandomGmReviewTarget("boss", config, null, () => 0)).toBeNull();
|
|
expect(drawRandomGmReviewTarget("manastorm", config, null, () => 0)).toBeNull();
|
|
});
|
|
|
|
it("updates the shared boss record and resolves exact review details", () => {
|
|
const target = gmReviewQueue("manastorm", DEFAULT_MANASTORM_ADMIN_CONFIG)[0];
|
|
expect(target?.kind).toBe("manastorm");
|
|
const updated = configWithGmReviewStatus(DEFAULT_MANASTORM_ADMIN_CONFIG, target!, "not-ready");
|
|
expect(updated).toBeTruthy();
|
|
const descriptor = describeGmReviewTarget(target!, updated!);
|
|
expect(descriptor).toMatchObject({ status: "not-ready", runnable: true });
|
|
if (target?.kind !== "dungeon") expect(updated!.bosses[target!.bossKey]?.status).toBe("not-ready");
|
|
});
|
|
|
|
it("keeps one shared review note across overlapping Boss and Manastorm targets", () => {
|
|
const overlap = resolvedBossAdminCatalog(DEFAULT_MANASTORM_ADMIN_CONFIG).find((entry) => (
|
|
entry.runnable
|
|
&& entry.source.includes("dungeon")
|
|
&& entry.source.includes("installed-manastorm")
|
|
));
|
|
expect(overlap).toBeTruthy();
|
|
const bossTarget = { kind: "boss", bossKey: overlap!.key } as const;
|
|
const manastormTarget = { kind: "manastorm", bossKey: overlap!.key } as const;
|
|
const noted = configWithGmReviewNote(
|
|
DEFAULT_MANASTORM_ADMIN_CONFIG,
|
|
bossTarget,
|
|
"Portal clips the west wall.",
|
|
)!;
|
|
const changedStatus = configWithGmReviewStatus(noted, manastormTarget, "not-ready")!;
|
|
|
|
expect(describeGmReviewTarget(manastormTarget, changedStatus)).toMatchObject({
|
|
status: "not-ready",
|
|
note: "Portal clips the west wall.",
|
|
});
|
|
expect(changedStatus.bosses[overlap!.key]).toMatchObject({
|
|
status: "not-ready",
|
|
note: "Portal clips the west wall.",
|
|
});
|
|
});
|
|
|
|
it("adds and clears advisory dungeon notes without changing availability", () => {
|
|
const dungeon = DUNGEON_CATALOG.find((entry) => entry.available)!;
|
|
const target = { kind: "dungeon", dungeonId: dungeon.id } as const;
|
|
const noted = configWithGmReviewNote(DEFAULT_MANASTORM_ADMIN_CONFIG, target, "Verify the final door.")!;
|
|
const cleared = configWithGmReviewNote(noted, target, "")!;
|
|
|
|
expect(describeGmReviewTarget(target, noted)).toMatchObject({ note: "Verify the final door.", runnable: true });
|
|
expect(cleared.dungeons[dungeon.id]?.note).toBeUndefined();
|
|
expect(dungeon.available).toBe(true);
|
|
});
|
|
});
|