feat: add traveler rotation shortcut
CI / verify (push) Successful in 2m10s
CI / build-and-deploy (push) Successful in 2m14s

This commit is contained in:
2026-09-20 00:00:30 +07:00 Unverified
parent 0d374b03ed
commit 3fe17a2ff7
4 changed files with 28 additions and 2 deletions
+20
View File
@@ -43,6 +43,26 @@ describe("rotation translator", () => {
const named: Team = { ...withRotation("Kaedehara Kazuha E"), builds: [{ ...build, characterKey: "kazuha", characterName: "Kaedehara Kazuha" }] };
expect(translateRotation(named).code).toContain("kazuha skill;");
});
it.each([
["traveler_anemo", "aetheranemo", "mc"],
["traveler_dendro", "aetherdendro", "MC"],
["traveler_cryo", "aethercryo", "Mc"],
])("resolves %s from the %s shortcut", (characterId, characterKey, shortcut) => {
const traveler = { ...build, id: characterId, characterId, characterKey, characterName: `${characterKey.slice(6)} Traveler` };
const result = translateRotation({ ...withRotation(`${shortcut} E`), builds: [traveler] });
expect(result).toEqual({ code: `active ${characterKey};\n\n${characterKey} skill;` });
});
it("only accepts mc when exactly one Traveler is in the shorthand team", () => {
expect(translateRotation(withRotation("mc E")).error).toContain("Unknown character");
const anemo = { ...build, id: "anemo", characterId: "traveler_anemo", characterKey: "aetheranemo", characterName: "Anemo Traveler" };
const dendro = { ...build, id: "dendro", characterId: "traveler_dendro", characterKey: "aetherdendro", characterName: "Dendro Traveler" };
expect(translateRotation({ ...withRotation("mc E"), builds: [anemo, dendro] }).error).toContain("ambiguous");
});
it("does not apply the mc shortcut to imported gcsim rotations", () => {
const traveler = { ...build, characterId: "traveler_dendro", characterKey: "aetherdendro", characterName: "Dendro Traveler" };
const imported: Team = { ...withRotation("mc skill;"), builds: [traveler], rotation: { enabled: true, text: "mc skill;", repeat: false, format: "gcsim" } };
expect(translateRotation(imported).error).toContain("not in this team");
});
it.each(["", "Mavuika", "Mavuika N0", "Mavuika 0(N)", "Mavuika 2()", "Mavuika 2(N", "Mavuika N)", "Mavuika W(0)", "Mavuika W(-1)", "Mavuika Z", "Mavuika E >", "Nahida E", "MavuikaExtra E", "Mavuika 2000(N2)", "Mavuika N2000 > Xilonen E"])("rejects invalid rotation %s", (text) => {
expect(translateRotation(withRotation(text)).error).toBeTruthy();
expect(translateRotation(withRotation(text)).code).toBe("");
+5 -1
View File
@@ -107,7 +107,10 @@ export function translateRotation(team: Team): RotationResult {
return { code: active ? body : `active ${team.builds[0].characterKey};\n\n${body}` };
} catch (error) { return { code: "", error: (error as Error).message }; }
}
const names = team.builds.flatMap((build) => [build.characterName, build.characterKey].map((name) => ({ name, key: build.characterKey }))).sort((a, b) => b.name.length - a.name.length);
const travelers = team.builds.filter((build) => build.characterId.startsWith("traveler_"));
const names = team.builds.flatMap((build) => [build.characterName, build.characterKey].map((name) => ({ name, key: build.characterKey })));
if (travelers.length === 1) names.push({ name: "mc", key: travelers[0].characterKey });
names.sort((a, b) => b.name.length - a.name.length);
const lines: string[] = [];
let firstCharacter = "";
let total = 0;
@@ -116,6 +119,7 @@ export function translateRotation(team: Team): RotationResult {
steps.forEach((raw, index) => {
const step = raw.trim();
if (!step) throw new Error(`Step ${index + 1}: Enter a character and actions (remove empty steps).`);
if (travelers.length > 1 && /^mc(?:\s|$)/i.test(step)) throw new Error(`Step ${index + 1}: 'mc' is ambiguous because this team has multiple Travelers. Use a Traveler name or key.`);
const character = names.find(({ name }) => step.slice(0, name.length).toLowerCase() === name.toLowerCase() && (step.length === name.length || /\s/.test(step[name.length])));
if (!character) throw new Error(`Step ${index + 1}: Unknown character. Use a name or key from this team.`);
firstCharacter ||= character.key;