import type { SearchRelease, SearchRuleVerdict } from "@umt/contracts"; import { describe, expect, it } from "vitest"; import { groupByWork, hrBadge, promoBadge, releaseSpecTags, verdictView, vsLibraryLabel } from "./search-labels.ts"; // SEARCH-5 资源行:规格标签拆分与顺序、促销 / H&R、按作品分组、规则判定文案。 const E1 = "11111111-1111-4111-8111-111111111111"; const E2 = "22222222-2222-4222-8222-222222222222"; const SLOT = "33333333-3333-4333-8333-333333333333"; const parsed = (p: Partial = {}): SearchRelease["parsed"] => ({ titleCn: null, titleEn: null, year: null, season: null, episodes: null, resolution: null, source: null, videoCodec: null, hdr: [], releaseGroup: null, ...p, }); const rel = (key: string, p: Partial = {}): SearchRelease => ({ key, sourceReleaseId: null, siteId: E1, siteName: "站", siteReleaseId: key, title: key, subtitle: null, category: { id: null, name: null }, sizeBytes: null, seeders: null, leechers: null, snatched: null, promotion: { type: "none", downloadFactor: 1, uploadFactor: 1, expiresAt: null }, free: false, hr: false, uploadedAt: null, detailUrl: "http://x/", observedAt: "2026-10-01T00:00:00.000Z", parsed: parsed(), mediaEntityId: null, entityMatch: null, entity: null, inventory: null, ruleVerdict: null, ...p, }); const verdict = (v: Partial = {}): SearchRuleVerdict => ({ slotId: SLOT, slotName: "主版本", slotCount: 1, outcome: "hit", tier: 1, tierName: "画质·第1层", failed: [], details: [], vsLibrary: null, ...v, }); describe("规格标签", () => { it("固定顺序:季集 → 分辨率 → 来源 → 编码 → HDR → 音频 → 语言 → 制作组", () => { const tags = releaseSpecTags( parsed({ season: 1, episodes: { start: 1, end: 8 }, releaseGroup: "CHDWEB", subtitles: ["zh-Hans", "en"], audioLangs: ["cmn"], audio: ["DDP 5.1", "Atmos"], hdr: ["DV", "HDR10"], videoCodec: "H.265", source: "WEB-DL", resolution: "2160p", }), ); expect(tags.map((t) => t.label)).toEqual([ "S01E01-E08", "2160p", "WEB-DL", "H.265", "DV", "HDR10", "DDP 5.1", "Atmos", "国语", "简中", "英字", "CHDWEB", ]); expect(tags.map((t) => t.dim)).toEqual([ "episode", "resolution", "source", "codec", "hdr", "hdr", "audio", "audio", "lang", "lang", "lang", "group", ]); expect(tags[1]!.title).toBe("分辨率:2160p"); }); it("没解析出的维度不出标签;同维度同名去重;多季合集写 S01-S03", () => { expect(releaseSpecTags(parsed())).toEqual([]); expect(releaseSpecTags(parsed({ hdr: ["DV", "DV"] })).map((t) => t.label)).toEqual(["DV"]); expect(releaseSpecTags(parsed({ season: 1, seasonEnd: 3 }))[0]!.label).toBe("S01-S03"); }); }); describe("促销与 H&R", () => { it("免费 / 2x 免费用 success;50%、2x 上传中性;无促销不显示", () => { expect(promoBadge(rel("a", { promotion: { ...rel("a").promotion, type: "free" }, free: true }))).toEqual({ label: "免费", tone: "success", }); expect(promoBadge(rel("a", { promotion: { ...rel("a").promotion, type: "2xfree" }, free: true }))).toEqual({ label: "2x 免费", tone: "success", }); expect(promoBadge(rel("a", { promotion: { ...rel("a").promotion, type: "50%" } }))).toEqual({ label: "50%", tone: "neutral", }); expect(promoBadge(rel("a", { promotion: { ...rel("a").promotion, type: "2x" } }))?.tone).toBe("neutral"); expect(promoBadge(rel("a"))).toBeNull(); }); it("H&R:有 → warning;未知 → 中性;没有 → 不显示", () => { expect(hrBadge({ hr: true })).toEqual({ label: "H&R", tone: "warning" }); expect(hrBadge({ hr: "unknown" })).toEqual({ label: "H&R 未知", tone: "neutral" }); expect(hrBadge({ hr: false })).toBeNull(); }); }); describe("按作品分组", () => { const work = (id: string, title: string) => ({ id, title, year: 2024, kind: "movie" as const }); it("多部作品按首次出现分组,未关联的放最后", () => { const rows = [ rel("a", { entity: work(E1, "沙丘2"), entityMatch: "alias" }), rel("b"), rel("c", { entity: work(E2, "奥本海默"), entityMatch: "resolved" }), rel("d", { entity: work(E1, "沙丘2"), entityMatch: "resolved" }), ]; const { grouped, groups } = groupByWork(rows); expect(grouped).toBe(true); expect(groups.map((g) => [g.key, g.items.map((r) => r.key)])).toEqual([ [E1, ["a", "d"]], [E2, ["c"]], [null, ["b"]], ]); // 组里有已识别的,组头不再标「未确认」 expect(groups[0]!.entityMatch).toBe("resolved"); }); it("只有一部作品 / 全部未关联时不分组", () => { expect( groupByWork([rel("a", { entity: work(E1, "沙丘2") }), rel("b", { entity: work(E1, "沙丘2") })]), ).toMatchObject({ grouped: false, groups: [{ key: E1 }], }); expect(groupByWork([rel("a"), rel("b")])).toMatchObject({ grouped: false, groups: [{ key: null }] }); expect(groupByWork([])).toEqual({ grouped: false, groups: [] }); }); }); describe("规则判定文案", () => { it("命中档位 / 满足 / 不满足 / 无法判断", () => { expect(verdictView(verdict())).toMatchObject({ label: "命中第 1 档", tone: "success" }); expect(verdictView(verdict({ tier: null, tierName: null })).label).toBe("满足收录条件"); const miss = verdictView( verdict({ outcome: "miss", tier: null, failed: ["分辨率", "来源"], details: ["分辨率 720p 不在允许列表"] }), ); expect(miss).toMatchObject({ label: "不满足:分辨率、来源", tone: "neutral" }); expect(miss.title).toContain("分辨率 720p 不在允许列表"); expect(verdictView(verdict({ outcome: "miss", tier: null, failed: ["分辨率", "来源", "编码", "HDR"] })).label).toBe( "不满足:分辨率、来源、编码 等 4 项", ); expect(verdictView(verdict({ outcome: "unknown", tier: null, failed: ["分辨率"] })).label).toBe("无法判断:分辨率"); }); it("多个收录版本时带收录版本名", () => { expect(verdictView(verdict({ slotCount: 2, slotName: "4K" })).label).toBe("「4K」命中第 1 档"); }); it("与库里版本比较", () => { expect(vsLibraryLabel(verdict({ vsLibrary: { cmp: "higher", steps: 1 } }))).toBe("比库里版本高 1 档"); expect(vsLibraryLabel(verdict({ vsLibrary: { cmp: "higher", steps: null } }))).toBe("比库里版本好"); expect(vsLibraryLabel(verdict({ vsLibrary: { cmp: "same", steps: 0 } }))).toBe("与库里版本同档"); expect(vsLibraryLabel(verdict({ vsLibrary: { cmp: "lower", steps: 2 } }))).toBe("不如库里版本"); expect(vsLibraryLabel(verdict())).toBeNull(); }); });