Files
UHD_Media_Tool/apps/web/app/components/episode-mapping/model.test.ts
T

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 条资源");
});
});