2026-06-09 13:14:11 +08:00

245 lines
8.0 KiB
JavaScript

import { afterEach, expect, test, vi } from "vitest";
import { parseResourceFolderFiles, resourcePlanToPayload, translateResourceCodes } from "./batchUpload.js";
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
});
test("parses folder resource filenames into resource plans", () => {
const files = [
file("头像框_星光_99_7_cover.png"),
file("头像框_星光_99_7_animation.svga"),
file("勋章_守护_长_cover.png"),
file("勋章_守护_长_animation.pag"),
file("背景卡_夜色_cover.png"),
file("背景卡_夜色_animation.svga"),
file("mic声波_律动_cover.png"),
file("mic声波_律动_animation.svga"),
file("礼物_玫瑰_520_cover.png"),
file("礼物_玫瑰_520_animation.svga"),
];
const plan = parseResourceFolderFiles(files, 1770000000000);
expect(plan.errors).toEqual([]);
expect(plan.resources.map((item) => [item.name, item.resourceType, item.price, item.badgeForm])).toEqual([
["星光", "avatar_frame", 99, "tile"],
["守护", "badge", 0, "strip"],
["夜色", "profile_card", 0, "tile"],
["律动", "mic_seat_animation", 0, "tile"],
["玫瑰", "gift", 520, "tile"],
]);
expect(plan.resources.map((item) => item.resourceCode)).toEqual(["星光", "守护", "夜色", "律动", "玫瑰"]);
expect(resourcePlanToPayload({ ...plan.resources[0], coverUrl: "cover", animationUrl: "animation" })).toMatchObject(
{
animationUrl: "animation",
coinPrice: 99,
previewUrl: "cover",
priceType: "coin",
resourceType: "avatar_frame",
},
);
expect(resourcePlanToPayload({ ...plan.resources[4], coverUrl: "cover", animationUrl: "animation" })).toMatchObject(
{
coinPrice: 520,
priceType: "coin",
resourceType: "gift",
},
);
expect(resourcePlanToPayload({ ...plan.resources[1], coverUrl: "cover", animationUrl: "animation" })).toMatchObject(
{
badgeForm: "strip",
badgeKind: "normal",
resourceType: "badge",
},
);
expect(
resourcePlanToPayload({
...plan.resources[2],
animationUrl: "animation",
coverUrl: "cover",
metadataJson:
'{"profile_card_layout":{"source_width":1136,"source_height":1680,"color_content_width":750,"content_top":117,"content_bottom":1666,"content_height":1550,"content_top_ratio":0.069643,"content_height_ratio":0.922619,"detect_version":1}}',
}),
).toMatchObject({
metadataJson: expect.stringContaining("profile_card_layout"),
resourceType: "profile_card",
});
});
test("silently ignores invalid and unpaired material", () => {
const plan = parseResourceFolderFiles(
[
file("frame/vip_TOPO2.png"),
file("气泡_清新_cover.png"),
file("头像框_星光_99_7_cover.png"),
file("头像框_星光_99_7_animation.svga"),
],
1770000000000,
);
expect(plan.errors).toEqual([]);
expect(plan.resources).toHaveLength(1);
expect(plan.resources[0]).toMatchObject({ name: "星光", resourceCode: "星光", resourceType: "avatar_frame" });
});
test("keeps mp4 alpha layout metadata for gift and vehicle batch payloads", () => {
const giftPayload = resourcePlanToPayload({
animationUrl: "https://media.haiyihy.com/resource/gift.mp4",
coverUrl: "cover",
metadataJson: JSON.stringify({ mp4_alpha_layout: leftAlphaRightRgbLayout() }),
name: "Love",
price: 30000,
resourceCode: "love",
resourceType: "gift",
});
const vehiclePayload = resourcePlanToPayload({
animationUrl: "https://media.haiyihy.com/resource/vehicle.mp4",
coverUrl: "cover",
metadataJson: JSON.stringify({ mp4_alpha_layout: leftAlphaRightRgbLayout() }),
name: "Knight",
price: 49000,
resourceCode: "knight",
resourceType: "vehicle",
});
expect(JSON.parse(giftPayload.metadataJson)).toMatchObject({
animation_format: "mp4",
mp4_alpha_layout: {
alpha_frame: [0, 0, 750, 1334],
alpha_layout: "alpha_left_rgb_right",
rgb_frame: [750, 0, 750, 1334],
},
});
expect(JSON.parse(vehiclePayload.metadataJson).mp4_alpha_layout.alpha_layout).toBe("alpha_left_rgb_right");
});
test("keeps profile card layout while adding mp4 alpha layout", () => {
const payload = resourcePlanToPayload({
animationUrl: "https://media.haiyihy.com/resource/profile-card.mp4",
coverUrl: "cover",
metadataJson: JSON.stringify({
mp4_alpha_layout: leftAlphaRightRgbLayout(),
profile_card_layout: {
color_content_width: 750,
content_bottom: 1666,
content_height: 1550,
content_height_ratio: 0.922619,
content_top: 117,
content_top_ratio: 0.069643,
detect_version: 1,
source_height: 1680,
source_width: 1136,
},
}),
name: "Night",
price: 0,
resourceCode: "night",
resourceType: "profile_card",
});
const metadata = JSON.parse(payload.metadataJson);
expect(metadata.profile_card_layout.content_top).toBe(117);
expect(metadata.mp4_alpha_layout.rgb_frame).toEqual([750, 0, 750, 1334]);
});
test("translates chinese names to english resource codes with API fallback", async () => {
vi.spyOn(Math, "random")
.mockReturnValueOnce(0)
.mockReturnValueOnce(51.5 / 52)
.mockReturnValueOnce(26 / 52)
.mockReturnValueOnce(27 / 52);
vi.stubGlobal(
"fetch",
vi.fn(async (url) => {
if (String(url).includes("mymemory")) {
return new Response(JSON.stringify({ responseData: { translatedText: "Starlight" } }));
}
return new Response("{}", { status: 500 });
}),
);
const translated = await translateResourceCodes([
{
name: "星光",
resourceCode: "星光",
resourceType: "avatar_frame",
},
{
name: "c11",
resourceCode: "c11",
resourceType: "avatar_frame",
},
]);
expect(translated.map((item) => item.resourceCode)).toEqual(["starlight_aZ", "c11_AB"]);
});
test("uses local dictionary when public translation APIs fail", async () => {
vi.spyOn(Math, "random").mockReturnValue(0);
vi.stubGlobal(
"fetch",
vi.fn(async () => new Response("{}", { status: 500 })),
);
const translated = await translateResourceCodes([
{
name: "守护",
resourceCode: "守护",
resourceType: "badge",
},
]);
expect(translated[0].resourceCode).toBe("guardian_aa");
});
test("reports translate progress after suffix is added", async () => {
vi.spyOn(Math, "random").mockReturnValue(0);
vi.stubGlobal(
"fetch",
vi.fn(async () => new Response("{}", { status: 500 })),
);
const onProgress = vi.fn();
await translateResourceCodes(
[
{
name: "守护",
resourceCode: "守护",
resourceType: "badge",
},
],
{ onProgress },
);
expect(onProgress).toHaveBeenCalledWith(
expect.objectContaining({
completed: 1,
index: 0,
resource: expect.objectContaining({ resourceCode: "guardian_aa" }),
total: 1,
}),
);
});
function file(name) {
return new File(["payload"], name, { type: "image/png" });
}
function leftAlphaRightRgbLayout() {
return {
alpha_frame: [0, 0, 750, 1334],
alpha_layout: "alpha_left_rgb_right",
confirmed: true,
detect_version: 1,
rgb_frame: [750, 0, 750, 1334],
video_h: 1334,
video_w: 1500,
};
}