feat(episode-mapping): 映射规则接口与模拟预览、待处理确认 / 忽略、作品页集数映射分区与订阅摘要
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
1 parent
fe597187dd
commit
45a114263b
26 files changed
+2770
-9
No files matched your search
@@ -0,0 +1,97 @@
|
||||
import type { Decision, EpisodeMappingPreview } from "@umt/contracts";
|
||||
import { createElement } from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { EpisodeDecisionBody, episodeDecisionView, simulationText } from "./decision.tsx";
|
||||
|
||||
const ENTITY = "7d3f7f5e-8a8c-4b8e-9f0a-1b2c3d4e5f60";
|
||||
const decision = (over: Partial<Decision> = {}): Decision =>
|
||||
({
|
||||
id: "11111111-1111-4111-8111-111111111111",
|
||||
kind: "episode_mapping",
|
||||
status: "open",
|
||||
severity: "warning",
|
||||
title: "集数待确认:药屋少女的呢喃 第 1 季 · Lilith-Raws",
|
||||
summary: "来源 S01E41:集数超出作品季表,也换算不出对应的季集(共 2 条资源)",
|
||||
subjectType: "media_entity",
|
||||
subjectId: ENTITY,
|
||||
dedupeKey: `episode_mapping:${ENTITY}:1:lilith-raws`,
|
||||
evidence: {
|
||||
mediaEntityId: ENTITY,
|
||||
season: 1,
|
||||
releaseGroup: "Lilith-Raws",
|
||||
count: 2,
|
||||
releases: [
|
||||
{
|
||||
sourceReleaseId: "a",
|
||||
title: "[Lilith-Raws] X - 40",
|
||||
source: { season: 1, episodes: { start: 40, end: 40 } },
|
||||
},
|
||||
{
|
||||
sourceReleaseId: "b",
|
||||
title: "[Lilith-Raws] X - 41",
|
||||
source: { season: 1, episodes: { start: 41, end: 41 } },
|
||||
},
|
||||
],
|
||||
suggestion: {
|
||||
expression: "EP-30",
|
||||
targetSeason: 2,
|
||||
samples: [
|
||||
{
|
||||
source: { start: 40, end: 40 },
|
||||
target: { season: 2, episode: 10 },
|
||||
publishedAt: "x",
|
||||
airDate: "2025-03-14",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{ key: "confirm_suggestion", label: "确认建议规则", description: "按 EP-30 换算到第 2 季" },
|
||||
{ key: "ignore_rule", label: "忽略并建忽略规则" },
|
||||
],
|
||||
...over,
|
||||
}) as Decision;
|
||||
|
||||
describe("待处理「集数待确认」", () => {
|
||||
it("从证据取范围、来源季集、建议规则与可用操作", () => {
|
||||
const v = episodeDecisionView(decision());
|
||||
expect(v).toMatchObject({ entityId: ENTITY, season: 1, releaseGroup: "Lilith-Raws", count: 2 });
|
||||
expect(v.releases.map((r) => r.source)).toEqual(["S01E40", "S01E41"]);
|
||||
expect(v.suggestion?.expression).toBe("EP-30");
|
||||
expect(v.confirm?.key).toBe("confirm_suggestion");
|
||||
expect(v.ignoreRule?.key).toBe("ignore_rule");
|
||||
const none = episodeDecisionView(decision({ options: [], evidence: { mediaEntityId: ENTITY, suggestion: null } }));
|
||||
expect(none).toMatchObject({ confirm: null, ignoreRule: null, suggestion: null, season: null });
|
||||
});
|
||||
|
||||
it("渲染来源季集、建议规则样本与模拟结果", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
createElement(EpisodeDecisionBody, { view: episodeDecisionView(decision()), simulation: "套用后 2 条都对得上" }),
|
||||
);
|
||||
expect(html).toContain("第 1 季 · Lilith-Raws");
|
||||
expect(html).toContain("S01E40");
|
||||
expect(html).toContain("EP-30");
|
||||
expect(html).toContain("第 2 季");
|
||||
expect(html).toContain("S02E10");
|
||||
expect(html).toContain("套用后 2 条都对得上");
|
||||
});
|
||||
|
||||
it("没有建议时提示到作品页手动建", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
createElement(EpisodeDecisionBody, {
|
||||
view: episodeDecisionView(decision({ evidence: { mediaEntityId: ENTITY, suggestion: null } })),
|
||||
simulation: null,
|
||||
}),
|
||||
);
|
||||
expect(html).toContain("到作品页手动建");
|
||||
});
|
||||
|
||||
it("模拟结果一句话", () => {
|
||||
const p = (vals: string[]) =>
|
||||
({ items: vals.map((validation) => ({ validation })) }) as unknown as EpisodeMappingPreview;
|
||||
expect(simulationText(p(["pass", "pass"]))).toBe("套用后 2 条都对得上");
|
||||
expect(simulationText(p(["pass", "fail"]))).toBe("套用后 1 条对得上,1 条仍对不上");
|
||||
expect(simulationText(p([]))).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user