fix(admin) : clarify validation and normalize percent input
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { finalizePercentInput, normalizePercentInput } from "@/lib/guides/percent-input";
|
||||
|
||||
describe("percentage input", () => {
|
||||
it("turns only a lone dash into zero when editing finishes", () => {
|
||||
expect(normalizePercentInput("-")).toBe("-");
|
||||
expect(finalizePercentInput("-")).toBe("0");
|
||||
expect(finalizePercentInput("-20")).toBe("-20");
|
||||
});
|
||||
|
||||
it("keeps positive and negative decimal input and ignores other text", () => {
|
||||
expect(normalizePercentInput("12.5")).toBe("12.5");
|
||||
expect(normalizePercentInput("-12.5")).toBe("-12.5");
|
||||
expect(normalizePercentInput("invalid", "12")).toBe("12");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
const DECIMAL = /^-?(?:\d+\.?\d*|\.\d*)?$/u;
|
||||
|
||||
export function normalizePercentInput(nextValue: string, currentValue = ""): string {
|
||||
const value = nextValue.trim();
|
||||
return DECIMAL.test(value) ? value : currentValue;
|
||||
}
|
||||
|
||||
export function finalizePercentInput(value: string): string {
|
||||
return value === "-" ? "0" : value;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { formatGuideValidationIssues } from "@/lib/guides/validation-messages";
|
||||
|
||||
describe("guide validation messages", () => {
|
||||
it("turns a raw weapon path into an actionable message", () => {
|
||||
expect(formatGuideValidationIssues("weapon", [{ path: ["rows", 5, "weaponKey"], message: "Invalid input" }])).toEqual([
|
||||
"อาวุธอันดับ 6: กรุณาเลือกอาวุธ",
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps specific Thai schema messages with a useful item label", () => {
|
||||
expect(formatGuideValidationIssues("team", [{ path: ["teams", 1, "members"], message: "Team ต้องมีตัวละครไม่ซ้ำกัน 4 ตัว" }])).toEqual([
|
||||
"Team 2: Team ต้องมีตัวละครไม่ซ้ำกัน 4 ตัว",
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
export type GuideValidationSection = "weapon" | "artifact" | "constellations" | "team" | "extra";
|
||||
|
||||
interface ValidationIssue {
|
||||
path: readonly PropertyKey[];
|
||||
message: string;
|
||||
}
|
||||
|
||||
const fieldMessages: Record<string, string> = {
|
||||
weaponKey: "กรุณาเลือกอาวุธ",
|
||||
firstArtifactKey: "กรุณาเลือก Artifact ชุดแรก",
|
||||
characterKey: "กรุณาเลือกตัวละคร",
|
||||
overallPercent: "กรุณากรอก Overall % ระหว่าง -9,999 ถึง 9,999",
|
||||
personalPercent: "กรุณากรอก Personal % ระหว่าง -9,999 ถึง 9,999",
|
||||
percent: "กรุณากรอกเปอร์เซ็นต์ระหว่าง -9,999 ถึง 9,999",
|
||||
cumulativePercent: "กรุณากรอก Cumulative % ระหว่าง -9,999 ถึง 9,999",
|
||||
title: "กรุณากรอกชื่อไม่เกิน 160 ตัวอักษร",
|
||||
heading: "กรุณากรอกหัวข้อไม่เกิน 200 ตัวอักษร",
|
||||
combo: "กรุณากรอก Combo",
|
||||
rotationSeconds: "กรุณากรอก Rotation เป็นตัวเลขมากกว่า 0",
|
||||
damage: "กรุณากรอก Damage เป็นตัวเลข 0 ขึ้นไป",
|
||||
};
|
||||
|
||||
const sectionLabels: Record<GuideValidationSection, string> = {
|
||||
weapon: "Weapon tier list",
|
||||
artifact: "Artifact",
|
||||
constellations: "Constellations",
|
||||
team: "Team Calc",
|
||||
extra: "Extra sections",
|
||||
};
|
||||
|
||||
function indexAfter(path: readonly PropertyKey[], key: string): number | null {
|
||||
const position = path.indexOf(key);
|
||||
const value = path[position + 1];
|
||||
return position >= 0 && typeof value === "number" ? value : null;
|
||||
}
|
||||
|
||||
function issueContext(section: GuideValidationSection, path: readonly PropertyKey[]): string {
|
||||
const rowIndex = indexAfter(path, "rows");
|
||||
const groupIndex = indexAfter(path, "groups");
|
||||
const teamIndex = indexAfter(path, "teams");
|
||||
const memberIndex = indexAfter(path, "members");
|
||||
const sectionIndex = indexAfter(path, "sections");
|
||||
|
||||
if (section === "weapon" && rowIndex !== null) return `อาวุธอันดับ ${rowIndex + 1}`;
|
||||
if (section === "artifact" && groupIndex !== null) {
|
||||
const group = groupIndex === 0 ? "Late Game" : "Early Game";
|
||||
return rowIndex === null ? `Artifact ${group}` : `Artifact ${group} อันดับ ${rowIndex + 1}`;
|
||||
}
|
||||
if (section === "constellations" && groupIndex !== null) {
|
||||
return rowIndex === null ? `Condition ${groupIndex + 1}` : `Condition ${groupIndex + 1} · C${rowIndex + 1}`;
|
||||
}
|
||||
if (section === "team" && teamIndex !== null) {
|
||||
return memberIndex === null ? `Team ${teamIndex + 1}` : `Team ${teamIndex + 1} · ตัวละคร ${memberIndex + 1}`;
|
||||
}
|
||||
if (section === "extra" && sectionIndex !== null) {
|
||||
return rowIndex === null ? `Extra section ${sectionIndex + 1}` : `Extra section ${sectionIndex + 1} · แถว ${rowIndex + 1}`;
|
||||
}
|
||||
return sectionLabels[section];
|
||||
}
|
||||
|
||||
function issueMessage(issue: ValidationIssue): string {
|
||||
const field = issue.path.at(-1);
|
||||
if (typeof field === "string" && fieldMessages[field]) return fieldMessages[field];
|
||||
if (/[-]/u.test(issue.message)) return issue.message;
|
||||
if (issue.message.startsWith("Too small")) return "กรุณากรอกข้อมูลให้ครบ";
|
||||
if (issue.message.startsWith("Too big")) return "ข้อมูลเกินจำนวนหรือความยาวที่กำหนด";
|
||||
return "กรุณาตรวจสอบข้อมูลให้ถูกต้อง";
|
||||
}
|
||||
|
||||
export function formatGuideValidationIssues(section: GuideValidationSection, issues: readonly ValidationIssue[]): string[] {
|
||||
return issues.map((issue) => `${issueContext(section, issue.path)}: ${issueMessage(issue)}`);
|
||||
}
|
||||
Reference in New Issue
Block a user