app-template/packages/danmaku/src/match.test.ts

159 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, it } from "vitest";
import { parseMatchOutcome, rankCandidates } from "./match.js";
describe("parseMatchOutcome", () => {
it("maps matches and drops bad episodeId", () => {
const r = parseMatchOutcome({
isMatched: true,
matches: [{ episodeId: 12, animeTitle: "A", episodeTitle: "01" }, { episodeId: "x" }],
});
expect(r.isMatched).toBe(true);
expect(r.candidates).toHaveLength(1);
expect(r.candidates[0]?.episodeId).toBe(12);
});
});
describe("rankCandidates", () => {
it("single candidate is high confidence", () => {
const r = rankCandidates([
{ episodeId: 1, animeId: 1, animeTitle: "A", episodeTitle: "01", imageUrl: null },
]);
expect(r.highConfidence).toBe(true);
expect(r.candidates).toHaveLength(1);
});
it("close scores are not high confidence", () => {
const list = [
{ episodeId: 1, animeId: 1, animeTitle: "Show", episodeTitle: "01", imageUrl: null },
{ episodeId: 2, animeId: 1, animeTitle: "Show", episodeTitle: "01v2", imageUrl: null },
];
const r = rankCandidates(list);
expect(r.highConfidence).toBe(false);
});
it("empty candidates are not high confidence", () => {
const r = rankCandidates([]);
expect(r.highConfidence).toBe(false);
expect(r.candidates).toEqual([]);
});
it("score gap >= default lead (0.15) is high confidence", () => {
const list = [
{ episodeId: 2, animeId: 1, animeTitle: "Show", episodeTitle: null, imageUrl: "img" },
{ episodeId: 1, animeId: 1, animeTitle: "Show", episodeTitle: "01", imageUrl: null },
];
const r = rankCandidates(list);
expect(r.highConfidence).toBe(true);
expect(r.candidates[0]?.episodeId).toBe(1);
});
it("custom lead is honored", () => {
const list = [
{ episodeId: 2, animeId: 1, animeTitle: "Show", episodeTitle: null, imageUrl: "img" },
{ episodeId: 1, animeId: 1, animeTitle: "Show", episodeTitle: "01", imageUrl: null },
];
expect(rankCandidates(list, { lead: 0.5 }).highConfidence).toBe(false);
expect(rankCandidates(list, { lead: 0.3 }).highConfidence).toBe(true);
});
it("returns candidates sorted by score desc", () => {
const hi = {
episodeId: 1,
animeId: 1,
animeTitle: "A",
episodeTitle: "01",
imageUrl: "img",
};
const mid = {
episodeId: 2,
animeId: 1,
animeTitle: "B",
episodeTitle: "02",
imageUrl: null,
};
const low = {
episodeId: 3,
animeId: null,
animeTitle: null,
episodeTitle: null,
imageUrl: "x",
};
const r = rankCandidates([low, mid, hi]);
expect(r.candidates.map((c) => c.episodeId)).toEqual([1, 2, 3]);
});
it("本地标题包含命中加分:多候选时命中者高置信", () => {
const list = [
{ episodeId: 1, animeId: 1, animeTitle: "迷宫饭", episodeTitle: "01", imageUrl: null },
{ episodeId: 2, animeId: 2, animeTitle: "芙莉莲", episodeTitle: "01", imageUrl: null },
];
const r = rankCandidates(list, { hint: { title: "葬送的芙莉莲" } });
expect(r.highConfidence).toBe(true);
expect(r.candidates[0]?.episodeId).toBe(2);
});
it("本地标题完全相等加分高于部分包含", () => {
const list = [
{
episodeId: 1,
animeId: 1,
animeTitle: "命运石之门",
episodeTitle: "01",
imageUrl: null,
},
{
episodeId: 2,
animeId: 2,
animeTitle: "命运石之门 负荷领域",
episodeTitle: "01",
imageUrl: null,
},
];
// 本地标题「命运石之门」:候选 1 完全相等(+1),候选 2 为包含(+0.6)
const r = rankCandidates(list, { hint: { title: "命运石之门" } });
expect(r.highConfidence).toBe(true);
expect(r.candidates[0]?.episodeId).toBe(1);
});
it("rawName 包含候选标题也加分", () => {
const list = [
{ episodeId: 1, animeId: 1, animeTitle: "迷宫饭", episodeTitle: "01", imageUrl: null },
{ episodeId: 2, animeId: 2, animeTitle: "芙莉莲", episodeTitle: "01", imageUrl: null },
];
const r = rankCandidates(list, {
hint: { title: "本地标题", rawName: "[Sub] Sousou no Frieren - 01.mkv" },
});
// rawName 不含任一候选,无加分 → 同分不高置信
expect(r.highConfidence).toBe(false);
const r2 = rankCandidates(list, {
hint: { title: "x", rawName: "[Sub] 芙莉莲 - 01.mkv" },
});
expect(r2.highConfidence).toBe(true);
expect(r2.candidates[0]?.episodeId).toBe(2);
});
it("单字符候选名不参与包含命中,避免噪声加分", () => {
const list = [
{ episodeId: 1, animeId: 1, animeTitle: "T", episodeTitle: "E", imageUrl: null },
{ episodeId: 2, animeId: 2, animeTitle: "T2", episodeTitle: "E2", imageUrl: null },
];
const r = rankCandidates(list, {
hint: { title: "Title", rawName: "Title - 01.mkv", epNumber: 1 },
});
expect(r.highConfidence).toBe(false);
});
it("集号相等加分拉开同标题候选差距", () => {
const list = [
{ episodeId: 1, animeId: 1, animeTitle: "Show", episodeTitle: "02", imageUrl: null },
{ episodeId: 2, animeId: 1, animeTitle: "Show", episodeTitle: "01", imageUrl: null },
];
const r = rankCandidates(list, { hint: { title: "Show", epNumber: 1 } });
expect(r.highConfidence).toBe(true);
expect(r.candidates[0]?.episodeId).toBe(2);
});
it("无 hint 时保持字段存在性打分(默认 lead 0.15)", () => {
const list = [
{ episodeId: 1, animeId: 1, animeTitle: "迷宫饭", episodeTitle: "01", imageUrl: null },
{ episodeId: 2, animeId: 2, animeTitle: "芙莉莲", episodeTitle: "01", imageUrl: null },
];
expect(rankCandidates(list).highConfidence).toBe(false);
});
});