Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
170 lines
6.1 KiB
TypeScript
170 lines
6.1 KiB
TypeScript
import type { EpisodeMappingPreview, EpisodeMappingRuleRow } from "@umt/contracts";
|
|
import { describe, expect, it } from "vitest";
|
|
import { EXCLUDE_REASON } from "../organize-wizard/model.ts";
|
|
import {
|
|
absoluteGroups,
|
|
canonicalText,
|
|
disabledReasonText,
|
|
draftFromSearch,
|
|
draftOfRule,
|
|
EMPTY_DRAFT,
|
|
groupMethodsText,
|
|
METHOD_LABEL,
|
|
mappingHref,
|
|
methodText,
|
|
parseTable,
|
|
reresolveToast,
|
|
ruleText,
|
|
sourceText,
|
|
specOfDraft,
|
|
} from "./model.ts";
|
|
|
|
describe("集数映射文案", () => {
|
|
it("方式名称给人话,覆盖全部方式", () => {
|
|
expect(METHOD_LABEL).toEqual({
|
|
raw: "原号",
|
|
rule: "按规则",
|
|
absolute_in_season: "季内绝对集号",
|
|
absolute_cross_season: "跨季绝对集号",
|
|
air_date: "按播出日期",
|
|
unverified: "元数据未覆盖,先按原号",
|
|
unresolved: "待确认",
|
|
skipped: "不适用",
|
|
});
|
|
expect(methodText({ method: "rule", special: "ignore" })).toBe("按规则:忽略");
|
|
expect(methodText(null)).toBe("未解析");
|
|
});
|
|
|
|
it("季集写法:原始 / 规范 / 特别篇 / 播出日期", () => {
|
|
expect(sourceText({ source: { season: 1, episodes: { start: 25, end: 25 } } })).toBe("S01E25");
|
|
expect(sourceText({ source: { episodes: { start: 13, end: 14 } } })).toBe("E13-E14");
|
|
expect(sourceText({ source: { airDate: "20250314" } })).toBe("2025-03-14");
|
|
expect(
|
|
canonicalText({ method: "absolute_cross_season", canonical: { season: 2, episodes: { start: 1, end: 1 } } }),
|
|
).toBe("S02E01");
|
|
expect(canonicalText({ method: "rule", canonical: null, special: "extra" })).toBe("花絮");
|
|
expect(canonicalText({ method: "unresolved", canonical: null })).toBe("—");
|
|
});
|
|
|
|
it("整理向导的排除原因有「集数待确认」与特典花絮", () => {
|
|
expect(EXCLUDE_REASON.episode_unresolved).toBe("集数待确认");
|
|
expect(EXCLUDE_REASON.special_extra).toBeTruthy();
|
|
});
|
|
|
|
it("停用原因:手动 / 规则失效", () => {
|
|
const rule = { status: "disabled", disabledReason: JSON.stringify({ manual: true }) } as EpisodeMappingRuleRow;
|
|
expect(disabledReasonText(rule)).toBe("手动停用");
|
|
expect(
|
|
disabledReasonText({
|
|
...rule,
|
|
disabledReason: JSON.stringify({ source: { season: 2, episodes: { start: 30, end: 30 } }, evidence: [] }),
|
|
}),
|
|
).toBe("规则算出的集数对不上季表(来源 S02E30),已自动停用");
|
|
expect(disabledReasonText({ ...rule, status: "active" })).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("对照表与草稿", () => {
|
|
it("对照表文本解析与回写", () => {
|
|
const t = parseTable("26=S02E01\n27-28 = s2e2\n");
|
|
expect(t.error).toBeNull();
|
|
expect(t.entries).toEqual([
|
|
{ from: { start: 26, end: 26 }, to: { season: 2, episode: 1 } },
|
|
{ from: { start: 27, end: 28 }, to: { season: 2, episode: 2 } },
|
|
]);
|
|
expect(ruleText({ kind: "table", tableEntries: t.entries })).toBe("26=S02E01 等 2 行");
|
|
expect(parseTable("26->").error).toContain("第 1 行");
|
|
expect(parseTable("28-27=S02E01").error).toContain("写反");
|
|
});
|
|
|
|
it("草稿 → 规则内容:不完整时给错误,不发预览", () => {
|
|
expect(specOfDraft({ ...EMPTY_DRAFT, expression: "" }).error).toContain("偏移表达式");
|
|
expect(specOfDraft({ ...EMPTY_DRAFT, season: "x", expression: "EP-12" }).error).toContain("来源季");
|
|
expect(specOfDraft({ ...EMPTY_DRAFT, season: "2", releaseGroup: " ANi ", expression: "EP-12" }).spec).toEqual({
|
|
season: 2,
|
|
releaseGroup: "ANi",
|
|
kind: "expression",
|
|
expression: "EP-12",
|
|
targetSeason: null,
|
|
});
|
|
expect(specOfDraft({ ...EMPTY_DRAFT, kind: "special", special: "extra" }).spec).toMatchObject({
|
|
kind: "special",
|
|
special: "extra",
|
|
season: null,
|
|
releaseGroup: null,
|
|
});
|
|
});
|
|
|
|
it("规则 → 草稿 → 规则内容 保持一致", () => {
|
|
const row = {
|
|
season: 1,
|
|
releaseGroup: "Lilith-Raws",
|
|
kind: "expression",
|
|
expression: "EP-30",
|
|
targetSeason: 2,
|
|
tableEntries: null,
|
|
special: null,
|
|
} as EpisodeMappingRuleRow;
|
|
expect(specOfDraft(draftOfRule(row)).spec).toEqual({
|
|
season: 1,
|
|
releaseGroup: "Lilith-Raws",
|
|
kind: "expression",
|
|
expression: "EP-30",
|
|
targetSeason: 2,
|
|
});
|
|
});
|
|
|
|
it("待处理「修改」的跳转地址能读回预填草稿", () => {
|
|
const href = mappingHref("/console/library/x", {
|
|
season: 1,
|
|
releaseGroup: "ANi",
|
|
expression: "EP-12",
|
|
targetSeason: 2,
|
|
});
|
|
expect(href).toBe("/console/library/x?mapSeason=1&mapGroup=ANi&mapExpr=EP-12&mapTarget=2#episode-mapping");
|
|
const sp = new URLSearchParams(href.slice(href.indexOf("?") + 1, href.indexOf("#")));
|
|
expect(draftFromSearch(sp)).toMatchObject({
|
|
season: "1",
|
|
releaseGroup: "ANi",
|
|
expression: "EP-12",
|
|
targetSeason: "2",
|
|
});
|
|
expect(draftFromSearch(new URLSearchParams())).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("按发布组统计", () => {
|
|
const preview = {
|
|
groups: [
|
|
{
|
|
releaseGroup: "ANi",
|
|
total: 5,
|
|
pending: 0,
|
|
suggestion: null,
|
|
methods: [
|
|
{ method: "raw", count: 1 },
|
|
{ method: "absolute_cross_season", count: 4 },
|
|
],
|
|
},
|
|
{ releaseGroup: null, total: 1, pending: 1, suggestion: null, methods: [{ method: "unresolved", count: 1 }] },
|
|
],
|
|
} as unknown as EpisodeMappingPreview;
|
|
it("一句话统计按数量排序", () => {
|
|
expect(groupMethodsText(preview.groups[0]!)).toBe("ANi:跨季绝对集号 4、原号 1");
|
|
expect(groupMethodsText(preview.groups[1]!)).toBe("未知发布组:待确认 1");
|
|
});
|
|
it("找出用绝对集号的发布组", () => {
|
|
expect(absoluteGroups(preview)).toEqual(["ANi"]);
|
|
expect(absoluteGroups(undefined)).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("应用后的提示", () => {
|
|
it("只列非零项", () => {
|
|
expect(reresolveToast({ releases: 12, changed: 2, resolved: 2 })).toBe(
|
|
"已应用:重算 12 条资源,2 条有变化,2 条待确认已解决",
|
|
);
|
|
expect(reresolveToast({ releases: 3, changed: 0, resolved: 0 }, "已停用")).toBe("已停用:重算 3 条资源");
|
|
});
|
|
});
|