134 lines
5.9 KiB
TypeScript
134 lines
5.9 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const mocks = vi.hoisted(() => ({ reads: [] as unknown[], rows: new Map<unknown, unknown[]>() }));
|
|
vi.mock("server-only", () => ({}));
|
|
vi.mock("@/lib/media/storage", () => ({ publicMediaUrl: (key: string) => `/media/${key}` }));
|
|
vi.mock("@/db", () => ({
|
|
getDb: () => ({
|
|
select: () => {
|
|
let table: unknown;
|
|
const query = {
|
|
from(value: unknown) { table = value; return query; },
|
|
innerJoin() { return query; },
|
|
leftJoin() { return query; },
|
|
where() { return query; },
|
|
orderBy() { return query; },
|
|
limit() { return query; },
|
|
then(resolve: (rows: unknown[]) => unknown) {
|
|
mocks.reads.push(table);
|
|
return Promise.resolve(mocks.rows.get(table) ?? []).then(resolve);
|
|
},
|
|
};
|
|
return query;
|
|
},
|
|
}),
|
|
}));
|
|
|
|
import {
|
|
artifactConditions, artifactProfiles, constellationConditions, extraSectionRows,
|
|
guides, guideSections, teamCompositions, teamConditions, teamMembers, weaponConditions,
|
|
catalogMaterials, guidePosterBleeding, media,
|
|
} from "@/db/schema";
|
|
import { getAdminGuideContent, listAdminTeamImportsForCharacter } from "./queries";
|
|
|
|
beforeEach(() => {
|
|
mocks.reads.length = 0;
|
|
mocks.rows.clear();
|
|
mocks.rows.set(guides, [{
|
|
guide: { id: "guide", slug: "test", posterArtMediaId: null },
|
|
cover: { objectKey: "cover", status: "ready" },
|
|
character: { key: "test", imageKey: "character", constellations: [], talents: [], ascension: [], leveling: [] },
|
|
}]);
|
|
mocks.rows.set(guideSections, [
|
|
{ id: "extra-a", slug: "extra-a", kind: "extra" },
|
|
{ id: "extra-b", slug: "extra-b", kind: "extra" },
|
|
]);
|
|
});
|
|
|
|
describe("admin section reads", () => {
|
|
it.each([
|
|
["weapon", weaponConditions],
|
|
["artifact", artifactConditions],
|
|
["constellations", constellationConditions],
|
|
["team", teamConditions],
|
|
])("loads only the %s section's conditions", async (section, expectedTable) => {
|
|
const result = await getAdminGuideContent("test", section);
|
|
expect(result).not.toBeNull();
|
|
expect(mocks.reads).toContain(expectedTable);
|
|
for (const table of [weaponConditions, artifactConditions, constellationConditions, teamConditions]) {
|
|
if (table !== expectedTable) expect(mocks.reads).not.toContain(table);
|
|
}
|
|
expect(mocks.reads).not.toContain(extraSectionRows);
|
|
expect(mocks.reads).not.toContain(catalogMaterials);
|
|
expect(mocks.reads).not.toContain(guidePosterBleeding);
|
|
});
|
|
|
|
it("loads just the selected extra section", async () => {
|
|
const result = await getAdminGuideContent("test", "extra-b");
|
|
expect(result?.extras.map((extra) => extra.id)).toEqual(["extra-b"]);
|
|
expect(mocks.reads).toContain(extraSectionRows);
|
|
expect(mocks.reads).not.toContain(weaponConditions);
|
|
expect(mocks.reads).not.toContain(teamConditions);
|
|
});
|
|
|
|
it("preserves full-guide reads for existing preview and validation callers", async () => {
|
|
const result = await getAdminGuideContent("test");
|
|
for (const table of [weaponConditions, artifactConditions, constellationConditions, teamConditions, extraSectionRows]) {
|
|
expect(mocks.reads).toContain(table);
|
|
}
|
|
expect(result?.extras).toHaveLength(2);
|
|
});
|
|
|
|
it("resolves condition icons from one batched media read", async () => {
|
|
const iconMediaId = "550e8400-e29b-41d4-a716-446655440000";
|
|
mocks.rows.set(weaponConditions, [{ id: "weapon-condition", iconMediaId }]);
|
|
mocks.rows.set(artifactConditions, [{ id: "artifact-condition", iconMediaId }]);
|
|
mocks.rows.set(constellationConditions, [{ id: "constellation-condition", iconMediaId }]);
|
|
mocks.rows.set(teamConditions, [{ id: "team-condition", iconMediaId }]);
|
|
mocks.rows.set(artifactProfiles, [{
|
|
sands: [], goblet: [], circlet: [], substats: [], targets: [],
|
|
recommendedConditions: [{ name: "Recommended", note: null, iconMediaId, sands: [], goblet: [], circlet: [], substats: [] }],
|
|
targetConditions: [{ name: "Target", note: null, iconMediaId, rows: [] }],
|
|
}]);
|
|
mocks.rows.set(media, [{ id: iconMediaId, objectKey: "condition-icon.png" }]);
|
|
|
|
const result = await getAdminGuideContent("test");
|
|
expect(result?.weaponGroups[0].iconUrl).toBe("/media/condition-icon.png");
|
|
expect(result?.artifactGroups[0].iconUrl).toBe("/media/condition-icon.png");
|
|
expect(result?.constellationGroups[0].iconUrl).toBe("/media/condition-icon.png");
|
|
expect(result?.teamGroups[0].iconUrl).toBe("/media/condition-icon.png");
|
|
expect(result?.artifactProfile?.recommendedConditions[0].iconUrl).toBe("/media/condition-icon.png");
|
|
expect(result?.artifactProfile?.targetConditions[0].iconUrl).toBe("/media/condition-icon.png");
|
|
expect(mocks.reads.filter((table) => table === media)).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
describe("team import lookup", () => {
|
|
it("hydrates matching teams and source labels with one member read", async () => {
|
|
mocks.rows.set(teamCompositions, [{
|
|
id: "team-1", title: "Example", rotationSeconds: "20", combo: "E > Q",
|
|
guideName: "Other Guide", conditionName: "Boss",
|
|
}]);
|
|
mocks.rows.set(teamMembers, [{
|
|
member: {
|
|
teamId: "team-1", characterKey: "character-1", weaponKey: "weapon-1",
|
|
artifactKey: "artifact-1", secondArtifactKey: null,
|
|
constellation: "2", refinement: "1", damage: "100",
|
|
},
|
|
characterName: "Character One",
|
|
}]);
|
|
|
|
const result = await listAdminTeamImportsForCharacter("current-guide", "character-1");
|
|
expect(result).toEqual([{
|
|
id: "team-1", title: "Example", rotationSeconds: "20", combo: "E > Q",
|
|
guideName: "Other Guide", conditionName: "Boss",
|
|
members: [{
|
|
characterKey: "character-1", characterName: "Character One", weaponKey: "weapon-1",
|
|
artifactKey: "artifact-1", secondArtifactKey: null,
|
|
constellation: "2", refinement: "1", damage: "100",
|
|
}],
|
|
}]);
|
|
expect(mocks.reads).toEqual([teamCompositions, teamMembers]);
|
|
});
|
|
});
|