- 新表 identify_words(0094):全局一块 + 每部作品一块原文,带版本与审计;episode_mapping_rule 转成文本行后删表
- 全局识别词移植 MP WordsMatcher(屏蔽 / 替换 / 集数偏移 / 替换+偏移 / {[tmdbid=…]}),所有按标题 / 文件名识别的地方解析前套用,命中的行记在解析结果上;保存后后台按改动行重新识别
- 作品识别词:S02 >> S01 EP+24 / S01E13 >> S00E01 / S01 @组 >> 忽略,解析器 + 打印器 + 编译;发布组繁简视为同一个
- 去掉规则自动停用:算出来对不上季表只待确认,要停用就注释掉那一行
- 集数待确认的采用 / 忽略与智能体 apply_rule 改为往作品识别词追加一行(带来历注释)
- 编辑器可视化 / 文本两种模式改同一份原文,保存前试算;作品页「集数映射」用同一个编辑器
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
95 lines
3.7 KiB
TypeScript
95 lines
3.7 KiB
TypeScript
import type { EpisodeMappingPreview } from "@umt/contracts";
|
|
import { describe, expect, it } from "vitest";
|
|
import { EXCLUDE_REASON } from "../organize-wizard/model.ts";
|
|
import {
|
|
absoluteGroups,
|
|
canonicalText,
|
|
groupMethodsText,
|
|
lineFromSearch,
|
|
METHOD_LABEL,
|
|
mappingHref,
|
|
methodText,
|
|
reresolveToast,
|
|
sourceText,
|
|
} from "./model.ts";
|
|
|
|
describe("集数映射文案", () => {
|
|
it("方式名称给人话,覆盖全部方式", () => {
|
|
expect(METHOD_LABEL).toEqual({
|
|
raw: "原号",
|
|
rule: "按作品识别词",
|
|
absolute_in_season: "季内绝对集号",
|
|
absolute_cross_season: "跨季绝对集号",
|
|
air_date: "按播出日期",
|
|
tmdb_season: "按 TMDB 默认季集换算",
|
|
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();
|
|
});
|
|
});
|
|
|
|
describe("作品页地址", () => {
|
|
it("集数待确认「修改」带上要追加的一行,作品页能读回", () => {
|
|
const href = mappingHref("/console/library/x", "S01 @ANi >> S02 EP-12");
|
|
expect(href).toBe("/console/library/x?mapLine=S01+%40ANi+%3E%3E+S02+EP-12#episode-mapping");
|
|
const sp = new URLSearchParams(href.slice(href.indexOf("?") + 1, href.indexOf("#")));
|
|
expect(lineFromSearch(sp)).toBe("S01 @ANi >> S02 EP-12");
|
|
expect(lineFromSearch(new URLSearchParams())).toBeNull();
|
|
expect(mappingHref("/console/library/x")).toBe("/console/library/x#episode-mapping");
|
|
});
|
|
});
|
|
|
|
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 条资源");
|
|
});
|
|
});
|