223 lines
7.2 KiB
TypeScript
223 lines
7.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
avatarIconName,
|
|
ascensionSnapshot,
|
|
baseStatsSnapshot,
|
|
levelingSnapshot,
|
|
characterDetailSchema,
|
|
cleanLunarisText,
|
|
constellationSnapshot,
|
|
parseLunarisText,
|
|
nameCardImageName,
|
|
splashImageName,
|
|
supportsWeaponType,
|
|
talentSnapshot,
|
|
} from "@/lib/catalog/lunaris";
|
|
|
|
describe("Lunaris character data", () => {
|
|
it("maps CardImg names to the avatar icon endpoint", () => {
|
|
expect(avatarIconName("UI_Gacha_AvatarIcon_Ineffa")).toBe(
|
|
"UI_AvatarIcon_Ineffa",
|
|
);
|
|
});
|
|
|
|
it("maps CardImg names to Lunaris character card assets", () => {
|
|
expect(splashImageName("UI_Gacha_AvatarIcon_Ayaka")).toBe(
|
|
"UI_Gacha_AvatarImg_Ayaka.png",
|
|
);
|
|
expect(nameCardImageName("UI_Gacha_AvatarIcon_Ayaka")).toBe(
|
|
"UI_NameCardPic_Ayaka_P.png",
|
|
);
|
|
expect(nameCardImageName("UI_Gacha_AvatarIcon_Qin")).toBe(
|
|
"UI_NameCardPic_Qin_P.png",
|
|
);
|
|
});
|
|
|
|
it("removes display markup from constellation text", () => {
|
|
expect(cleanLunarisText('"A {LINK#N1}<color=#fff>bright</color>{/LINK} morning"')).toBe("A bright morning");
|
|
});
|
|
|
|
it("converts escaped Lunaris newlines into displayable lines", () => {
|
|
expect(cleanLunarisText("First line\\n\\n<i>Second line</i>")).toBe("First line\n\nSecond line");
|
|
});
|
|
|
|
it("parses Lunaris links and colors into safe display nodes", () => {
|
|
const links = new Map([["N11500003", {
|
|
name: "Marvelous Splendor",
|
|
description: "A special stack effect.",
|
|
}]]);
|
|
|
|
expect(parseLunarisText(
|
|
"Gain {LINK#N11500003}Marvelous Splendor{/LINK} and deal <color=#99FFFFFF>Cryo AoE DMG</color>.",
|
|
links,
|
|
)).toEqual([
|
|
{ type: "text", value: "Gain " },
|
|
{
|
|
type: "link",
|
|
target: "N11500003",
|
|
detail: { name: "Marvelous Splendor", description: "A special stack effect." },
|
|
children: [{ type: "text", value: "Marvelous Splendor" }],
|
|
},
|
|
{ type: "text", value: " and deal " },
|
|
{
|
|
type: "color",
|
|
value: "#99FFFFFF",
|
|
children: [{ type: "text", value: "Cryo AoE DMG" }],
|
|
},
|
|
{ type: "text", value: "." },
|
|
]);
|
|
});
|
|
|
|
it("resolves named skill links and ignores malformed display tags", () => {
|
|
const skills = new Map([["Slow Dance", {
|
|
name: "Slow Dance",
|
|
description: "Skill detail",
|
|
}]]);
|
|
|
|
expect(parseLunarisText(
|
|
"{LINK#S11502}<color=#FFD780FF>Slow Dance</color>{/LINK}</color>\n<color=javascript:alert(1)>Safe</color>",
|
|
new Map(),
|
|
skills,
|
|
)).toEqual([
|
|
{
|
|
type: "link",
|
|
target: "S11502",
|
|
detail: { name: "Slow Dance", description: "Skill detail" },
|
|
children: [{
|
|
type: "color",
|
|
value: "#FFD780FF",
|
|
children: [{ type: "text", value: "Slow Dance" }],
|
|
}],
|
|
},
|
|
{ type: "text", value: "\nSafe" },
|
|
]);
|
|
});
|
|
|
|
it("normalizes constellation levels and icon keys", () => {
|
|
const result = constellationSnapshot({
|
|
constellations: {
|
|
const1: { name: '"First"', description: "Detail", icon: "UI_Talent_Test_01" },
|
|
},
|
|
}, (icon) => `catalog/1/constellations/${icon}.webp`);
|
|
expect(result).toEqual([{
|
|
level: 1,
|
|
name: "First",
|
|
description: "Detail",
|
|
descriptionNodes: [{ type: "text", value: "Detail" }],
|
|
iconKey: "catalog/1/constellations/UI_Talent_Test_01.webp",
|
|
}]);
|
|
});
|
|
|
|
it("accepts characters that do not have constellations", () => {
|
|
const detail = characterDetailSchema.parse({ constellations: null });
|
|
|
|
expect(constellationSnapshot(detail, (icon) => icon)).toEqual([]);
|
|
});
|
|
|
|
it("normalizes scalar and grouped ascension materials", () => {
|
|
const detail = characterDetailSchema.parse({
|
|
ascension: {
|
|
"202": 7049900,
|
|
exp: { "104001": 2, "104003": 418 },
|
|
},
|
|
});
|
|
|
|
expect(ascensionSnapshot(detail)).toEqual([
|
|
{ type: "202", items: [{ key: "202", amount: 7049900 }] },
|
|
{
|
|
type: "exp",
|
|
items: [
|
|
{ key: "104001", amount: 2 },
|
|
{ key: "104003", amount: 418 },
|
|
],
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("normalizes leveling materials by category", () => {
|
|
const detail = characterDetailSchema.parse({
|
|
skills: {
|
|
leveling: {
|
|
crown: { "104319": 3 },
|
|
books: { "104365": 9, "104366": 63, "104367": 114 },
|
|
worldmonster: { "112146": 18, "112147": 66, "112148": 93 },
|
|
weekly: { "113088": 18 },
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(levelingSnapshot(detail)).toEqual([
|
|
{ type: "crown", items: [{ key: "104319", amount: 3 }] },
|
|
{ type: "books", items: [{ key: "104365", amount: 9 }, { key: "104366", amount: 63 }, { key: "104367", amount: 114 }] },
|
|
{ type: "worldmonster", items: [{ key: "112146", amount: 18 }, { key: "112147", amount: 66 }, { key: "112148", amount: 93 }] },
|
|
{ type: "weekly", items: [{ key: "113088", amount: 18 }] },
|
|
]);
|
|
});
|
|
|
|
it("selects dynamic level 90 base stats", () => {
|
|
const detail = characterDetailSchema.parse({
|
|
attributes: [
|
|
{ level: 1, ascension: 0, hp: 1, atk: 2, def: 3 },
|
|
{ level: 90, ascension: 6, hp: 12981, atk: 335, def: 787, "CRIT DMG%": "38.4" },
|
|
],
|
|
});
|
|
|
|
expect(baseStatsSnapshot(detail)).toEqual([
|
|
{ key: "hp", value: 12981 },
|
|
{ key: "atk", value: 335 },
|
|
{ key: "def", value: 787 },
|
|
{ key: "CRIT DMG%", value: "38.4" },
|
|
]);
|
|
});
|
|
|
|
it("selects base stats nested under info", () => {
|
|
const detail = characterDetailSchema.parse({
|
|
info: { attributes: [{ level: 90, hp: 100, atk: 20, def: 30, EM: 40 }] },
|
|
});
|
|
|
|
expect(baseStatsSnapshot(detail)).toEqual([
|
|
{ key: "hp", value: 100 },
|
|
{ key: "atk", value: 20 },
|
|
{ key: "def", value: 30 },
|
|
{ key: "EM", value: 40 },
|
|
]);
|
|
});
|
|
|
|
it("reads the supported weapon type from character details", () => {
|
|
const detail = characterDetailSchema.parse({
|
|
info: { element: "Cryo", weapon: "WEAPON_SWORD_ONE_HAND" },
|
|
constellations: null,
|
|
});
|
|
|
|
expect(detail.info?.weapon).toBe("WEAPON_SWORD_ONE_HAND");
|
|
expect(detail.info?.element).toBe("Cryo");
|
|
});
|
|
|
|
it("maps normal, skill, and burst talent metadata", () => {
|
|
const detail = characterDetailSchema.parse({
|
|
skills: {
|
|
normalattack: { name: "Normal", description: "Line 1\\nLine 2", icon: "Skill_A_Test" },
|
|
elementalskill: { name: "Skill", description: "Skill detail", icon: "Skill_S_Test" },
|
|
elementalburst: { name: "Burst", description: "Burst detail", icon: "Skill_E_Test" },
|
|
},
|
|
});
|
|
|
|
expect(talentSnapshot(detail, (icon) => `${icon}.webp`)).toEqual([
|
|
{ type: "NA", name: "Normal", description: "Line 1\nLine 2", iconKey: "Skill_A_Test.webp" },
|
|
{ type: "E", name: "Skill", description: "Skill detail", iconKey: "Skill_S_Test.webp" },
|
|
{ type: "Q", name: "Burst", description: "Burst detail", iconKey: "Skill_E_Test.webp" },
|
|
]);
|
|
});
|
|
|
|
it("matches only weapons supported by the character", () => {
|
|
expect(
|
|
supportsWeaponType("WEAPON_SWORD_ONE_HAND", "WEAPON_SWORD_ONE_HAND"),
|
|
).toBe(true);
|
|
expect(
|
|
supportsWeaponType("WEAPON_SWORD_ONE_HAND", "WEAPON_CATALYST"),
|
|
).toBe(false);
|
|
expect(supportsWeaponType(undefined, "WEAPON_SWORD_ONE_HAND")).toBe(false);
|
|
});
|
|
});
|