feat : update
CI / Verify (push) Successful in 50s
CI / Build immutable images and deploy (push) Successful in 4m15s

This commit is contained in:
2026-08-30 11:17:58 +00:00 Unverified
parent fd3472bc8d
commit 8177c91434
9 changed files with 257 additions and 18 deletions
+7
View File
@@ -35,6 +35,13 @@ describe("structured guide validation", () => {
expect(artifactSectionSchema.safeParse({ ...base, talents: [{ type: "NA", priority: 1, level: "11" }] }).success).toBe(false);
});
it("accepts talent comparison relations while keeping legacy talents valid", () => {
const base = { expectedVersion: 1, groups: artifactGroups, sands: [], goblet: [], circlet: [], substats: [], targets: [], note: null };
expect(artifactSectionSchema.safeParse({ ...base, talents: [{ type: "E", priority: 1, relationToNext: "gte" }] }).success).toBe(true);
expect(artifactSectionSchema.safeParse({ ...base, talents: [{ type: "E", priority: 1 }] }).success).toBe(true);
expect(artifactSectionSchema.safeParse({ ...base, talents: [{ type: "E", priority: 1, relationToNext: "equal" }] }).success).toBe(false);
});
it("requires four unique team members and non-zero damage", () => {
const member = (characterKey: string) => ({ characterKey, weaponKey: null, artifactKey: null, constellation: 0, refinement: 1, damage: 100 });
const base = { expectedVersion: 1, teams: [{ title: "Test", rotationSeconds: 20, combo: "E > Q", members: ["1", "2", "3", "4"].map(member) }] };
+2
View File
@@ -38,6 +38,7 @@ export const mainStatOptions = {
export const substatOptions = ["CRIT Rate%/CRIT DMG%", "HP%", "ATK%", "DEF%", "EM", "ER"] as const;
export const talentLevelOptions = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"] as const;
export const talentRelationOptions = ["gt", "gte"] as const;
const artifactGroupSchema = (name: "Late Game" | "Early Game") => z.strictObject({
name: z.literal(name),
@@ -66,6 +67,7 @@ export const artifactSectionSchema = z.strictObject({
type: z.enum(["NA", "E", "Q"]),
priority: z.number().int().min(1).max(3),
level: z.enum(talentLevelOptions).optional(),
relationToNext: z.enum(talentRelationOptions).optional(),
})).max(3),
note: z.string().trim().max(2_000).nullable(),
});
+35
View File
@@ -0,0 +1,35 @@
import { describe, expect, it } from "vitest";
import { normalizeTalentPriorities, reorderTalentPriorities, talentRelationSymbol } from "@/lib/guides/talents";
describe("talent priorities", () => {
it("normalizes legacy talents to strict comparisons", () => {
expect(normalizeTalentPriorities([
{ type: "Q", priority: 2 },
{ type: "E", priority: 1 },
{ type: "NA", priority: 3 },
])).toEqual([
{ type: "E", priority: 1, relationToNext: "gt" },
{ type: "Q", priority: 2, relationToNext: "gt" },
{ type: "NA", priority: 3, relationToNext: undefined },
]);
});
it("reorders talents while preserving relations between positions", () => {
expect(reorderTalentPriorities([
{ type: "E", priority: 1, relationToNext: "gte" },
{ type: "Q", priority: 2, relationToNext: "gt" },
{ type: "NA", priority: 3 },
], "NA", "E")).toEqual([
{ type: "NA", priority: 1, relationToNext: "gte" },
{ type: "E", priority: 2, relationToNext: "gt" },
{ type: "Q", priority: 3, relationToNext: undefined },
]);
});
it("formats strict and greater-than-or-equal relations", () => {
expect(talentRelationSymbol("gt")).toBe(">");
expect(talentRelationSymbol("gte")).toBe("≥");
expect(talentRelationSymbol()).toBe(">");
});
});
+43
View File
@@ -0,0 +1,43 @@
export type TalentRelation = "gt" | "gte";
export interface TalentPriority {
type: "NA" | "E" | "Q";
priority: number;
level?: string;
relationToNext?: TalentRelation;
}
export function normalizeTalentPriorities(talents: readonly TalentPriority[]): TalentPriority[] {
const ordered = [...talents].sort((a, b) => a.priority - b.priority);
return ordered.map((talent, index) => ({
...talent,
priority: index + 1,
relationToNext: index < ordered.length - 1 ? talent.relationToNext ?? "gt" : undefined,
}));
}
export function reorderTalentPriorities(
talents: readonly TalentPriority[],
activeType: TalentPriority["type"],
overType: TalentPriority["type"],
): TalentPriority[] {
const ordered = normalizeTalentPriorities(talents);
const from = ordered.findIndex((talent) => talent.type === activeType);
const to = ordered.findIndex((talent) => talent.type === overType);
if (from < 0 || to < 0 || from === to) return ordered;
const relations = ordered.slice(0, -1).map((talent) => talent.relationToNext ?? "gt");
const next = [...ordered];
const [moved] = next.splice(from, 1);
next.splice(to, 0, moved);
return next.map((talent, index) => ({
...talent,
priority: index + 1,
relationToNext: index < next.length - 1 ? relations[index] : undefined,
}));
}
export function talentRelationSymbol(relation?: TalentRelation): ">" | "≥" {
return relation === "gte" ? "≥" : ">";
}