feat(admin-images) : add standalone export generators
CI / Verify (push) Successful in 1m29s
CI / Build immutable images and deploy (push) Failing after 1m42s

This commit is contained in:
2026-09-29 17:13:24 +07:00 Unverified
parent 76478fe56d
commit db554228bc
21 changed files with 1179 additions and 64 deletions
+44
View File
@@ -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 &gt; 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" },
+26
View File
@@ -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();
});
});
+21
View File
@@ -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;
}
}
+35
View File
@@ -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");
});
});