245 lines
8.0 KiB
JavaScript
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,
|
|
};
|
|
}
|