feat(admin-images) : add standalone export generators
This commit is contained in:
@@ -10,9 +10,53 @@ import {
|
||||
import { MaterialPoster } from "@/components/public/material-poster";
|
||||
import { ConstellationImage } from "@/components/public/constellation-image";
|
||||
import { WeaponComparisonImage } from "@/components/public/weapon-image-export";
|
||||
import { TeamExportImage, type TeamExportData } from "@/components/public/team-search";
|
||||
import type { PublicGuide } from "@/lib/guides/queries";
|
||||
|
||||
describe("image export controls", () => {
|
||||
it.each([1, 4])("renders the shared Team Calc image with %i member(s)", (memberCount) => {
|
||||
const members: TeamExportData["members"] = Array.from({ length: memberCount }, (_, position) => ({
|
||||
position,
|
||||
constellation: String(position),
|
||||
refinement: "1",
|
||||
damage: "1000",
|
||||
character: {
|
||||
key: `character-${position}`,
|
||||
name: `Character ${position + 1}`,
|
||||
imageUrl: "/character.png",
|
||||
element: "Fire",
|
||||
rarity: 5,
|
||||
},
|
||||
weapon: {
|
||||
key: `weapon-${position}`,
|
||||
name: `Weapon ${position + 1}`,
|
||||
imageUrl: "/weapon.png",
|
||||
rarity: 5,
|
||||
},
|
||||
artifact: {
|
||||
key: `artifact-${position}`,
|
||||
name: `Artifact ${position + 1}`,
|
||||
imageUrl: "/artifact.png",
|
||||
rarity: 5,
|
||||
},
|
||||
secondArtifact: null,
|
||||
}));
|
||||
const team: TeamExportData = {
|
||||
title: "Generator Team",
|
||||
rotationSeconds: "20",
|
||||
combo: "E > Q",
|
||||
members,
|
||||
};
|
||||
|
||||
const html = renderToStaticMarkup(<TeamExportImage team={team} />);
|
||||
|
||||
expect(html).toContain("Generator Team");
|
||||
expect(html).toContain("Rotation duration: 20 seconds");
|
||||
expect(html).toContain("E > Q");
|
||||
expect(html).toContain(`DPS: ${memberCount * 50}`);
|
||||
expect(html.match(/Character \d/gu)).toHaveLength(memberCount);
|
||||
});
|
||||
|
||||
it("omits the placeholder condition from weapon export images", () => {
|
||||
const guide = {
|
||||
character: { name: "Raiden", splashImageUrl: "/splash.png" },
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
IMAGE_GENERATOR_DRAFT_VERSION,
|
||||
parseImageGeneratorDraft,
|
||||
serializeImageGeneratorDraft,
|
||||
} from "./image-generator-draft";
|
||||
|
||||
describe("image generator drafts", () => {
|
||||
const parseTitle = (value: unknown) => {
|
||||
if (!value || typeof value !== "object") return null;
|
||||
const title = (value as { title?: unknown }).title;
|
||||
return typeof title === "string" ? { title } : null;
|
||||
};
|
||||
|
||||
it("round trips a versioned draft", () => {
|
||||
const serialized = serializeImageGeneratorDraft({ title: "Hyperbloom" });
|
||||
expect(parseImageGeneratorDraft(serialized, parseTitle)).toEqual({ title: "Hyperbloom" });
|
||||
});
|
||||
|
||||
it("rejects corrupt, incompatible, and invalid drafts", () => {
|
||||
expect(parseImageGeneratorDraft("not-json", parseTitle)).toBeNull();
|
||||
expect(parseImageGeneratorDraft(JSON.stringify({ version: IMAGE_GENERATOR_DRAFT_VERSION + 1, value: { title: "Old" } }), parseTitle)).toBeNull();
|
||||
expect(parseImageGeneratorDraft(JSON.stringify({ version: IMAGE_GENERATOR_DRAFT_VERSION, value: { title: 42 } }), parseTitle)).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
export const IMAGE_GENERATOR_DRAFT_VERSION = 1;
|
||||
|
||||
export function serializeImageGeneratorDraft<Value>(value: Value): string {
|
||||
return JSON.stringify({ version: IMAGE_GENERATOR_DRAFT_VERSION, value });
|
||||
}
|
||||
|
||||
export function parseImageGeneratorDraft<Value>(
|
||||
serialized: string,
|
||||
parse: (value: unknown) => Value | null,
|
||||
): Value | null {
|
||||
try {
|
||||
const envelope = JSON.parse(serialized) as {
|
||||
version?: unknown;
|
||||
value?: unknown;
|
||||
};
|
||||
if (envelope.version !== IMAGE_GENERATOR_DRAFT_VERSION) return null;
|
||||
return parse(envelope.value);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { ConstellationImageGenerator } from "@/components/admin/constellation-image-generator";
|
||||
import { TeamImageGenerator } from "@/components/admin/team-image-generator";
|
||||
import { WeaponImageGenerator } from "@/components/admin/weapon-image-generator";
|
||||
|
||||
describe("admin image generator empty states", () => {
|
||||
it("starts Team with a create action instead of an empty form", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<TeamImageGenerator characters={[]} weapons={[]} artifacts={[]} />,
|
||||
);
|
||||
expect(html).toContain("ยังไม่มี Team");
|
||||
expect(html).toContain("เพิ่ม Team");
|
||||
expect(html).not.toContain("team-image-title");
|
||||
});
|
||||
|
||||
it("starts Weapon with a create action instead of an empty row", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<WeaponImageGenerator characters={[]} weapons={[]} />,
|
||||
);
|
||||
expect(html).toContain("ยังไม่มีรายการอาวุธ");
|
||||
expect(html).toContain("เพิ่มรายการอาวุธ");
|
||||
expect(html).not.toContain("weapon-image-condition");
|
||||
});
|
||||
|
||||
it("starts Constellation with one action that creates C1–C6", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<ConstellationImageGenerator characters={[]} />,
|
||||
);
|
||||
expect(html).toContain("ยังไม่มี Constellation");
|
||||
expect(html).toContain("สร้าง Constellation");
|
||||
expect(html).not.toContain("constellation-image-c1");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user