Files
UHD_Media_Tool/packages/domain/test/release-facets.test.ts
T
rootandClaude Opus 5.5 f6d976a963 feat(search): 筛选选项只列结果里有的值,季集合并成一个维度放最前
对照 MP useTorrentFilter:分面选项按实际结果生成,计数为 0 的不再列出(已选的值仍列出,可删除);
新增季集维度 seasonEpisode(MP season_episode 写法 `S01` / `S01-S02` / `S02 E14` / `S01 E25-E48`),
整季在前、单集在后、季号新的在前,在筛选条最前。旧的季 / 集维度保留(旧链接仍生效,以标签列出)。
搜索结果页与站点资源 Tab 共用这套口径。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
2026-10-06 15:39:32 +08:00

341 lines
13 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 {
applyFacets,
audioBuckets,
compareSeasonEpisode,
type FacetInput,
formatFacetParam,
parseFacetParam,
parseRelease,
promotionBuckets,
releaseTypeBucket,
seasonEpisodeValue,
seedersBucket,
sizeBucket,
subtitleBuckets,
} from "../src/index.ts";
// SEARCH-3 快速筛选:档位、计数(按其它维度已选条件)、同维度 OR / 跨维度 AND、qf 编解码
const NOW = Date.parse("2026-09-29T00:00:00Z");
const none = { type: "none", downloadFactor: 1, uploadFactor: 1, expiresAt: null };
const mk = (id: string, o: Partial<FacetInput> = {}): FacetInput & { id: string } => ({
id,
siteId: "a",
siteName: "站 A",
resolution: "1080p",
source: "WEB-DL",
videoCodec: "H.264",
hdr: [],
releaseGroup: null,
season: null,
episodes: null,
promotion: none,
...o,
});
const items = [
mk("1", {
resolution: "2160p",
source: "REMUX",
videoCodec: "H.265",
hdr: ["DV", "HDR10"],
releaseGroup: "FraMeSToR",
}),
mk("2", {
resolution: "2160p",
source: "WEB-DL",
videoCodec: "H.265",
hdr: ["HDR10"],
releaseGroup: "FLUX",
siteId: "b",
siteName: "站 B",
}),
mk("3", {
resolution: "1080p",
source: "BluRay",
videoCodec: "H.264",
releaseGroup: "flux",
promotion: { type: "free", downloadFactor: 0, uploadFactor: 1, expiresAt: null },
}),
mk("4", {
resolution: "720p",
source: "HDTV",
videoCodec: null,
promotion: { type: "2xfree", downloadFactor: 0, uploadFactor: 2, expiresAt: null },
}),
mk("5", { resolution: null, source: "BluRay-Disc", videoCodec: "AV1", siteId: "b", siteName: "站 B" }),
];
const run = (sel: Parameters<typeof applyFacets>[2]) => applyFacets(items, (x) => x, sel, { now: NOW });
const ids = (r: { items: { id: string }[] }) => r.items.map((i) => i.id);
const counts = (opts: { value: string; count: number }[]) => Object.fromEntries(opts.map((o) => [o.value, o.count]));
describe("release-facets", () => {
it("未筛选:各维度计数等于全部结果里的档位分布", () => {
const r = run({});
expect(ids(r)).toEqual(["1", "2", "3", "4", "5"]);
expect(counts(r.facets.resolution)).toEqual({ "2160p": 2, "1080p": 1, "720p": 1, other: 1 });
expect(counts(r.facets.videoCodec)).toEqual({ "H.265": 2, "H.264": 1, AV1: 1, other: 1 });
expect(counts(r.facets.releaseType)).toEqual({ disc: 1, remux: 1, encode: 1, webdl: 1, hdtv: 1 });
expect(r.facets.releaseType.map((o) => o.label)).toEqual(["原盘", "Remux", "蓝光压制", "WEB-DL", "HDTV"]);
// 结果里没有的档位不列出(HDR10+、50%)
expect(counts(r.facets.hdr)).toEqual({ DV: 1, HDR10: 2, SDR: 3 });
expect(counts(r.facets.promotion)).toEqual({ free: 2, "2x": 1, none: 3 });
expect(r.facets.site).toEqual([
{ value: "a", label: "站 A", count: 3 },
{ value: "b", label: "站 B", count: 2 },
]);
// 制作组不分大小写合并
expect(r.facets.releaseGroup).toEqual([
{ value: "FLUX", label: "FLUX", count: 2 },
{ value: "FraMeSToR", label: "FraMeSToR", count: 1 },
]);
expect(r.facets.season).toEqual([]);
expect(r.facets.seasonEpisode).toEqual([]);
});
it("同维度 OR、不同维度 AND;计数按其它维度的已选条件", () => {
const r = run({ resolution: ["2160p", "1080p"], videoCodec: ["H.265"] });
expect(ids(r)).toEqual(["1", "2"]);
// 分辨率的计数只受编码条件影响:1080p 在 H.265 下没有结果,但已选,仍列出(计数 0);720p / 其他不列
expect(counts(r.facets.resolution)).toEqual({ "2160p": 2, "1080p": 0 });
// 编码计数只受分辨率条件影响:在 2160p / 1080p 下没有 AV1 / 其他,不列
expect(counts(r.facets.videoCodec)).toEqual({ "H.265": 2, "H.264": 1 });
// 其它维度按全部条件:计数为 0 的档位不列
expect(counts(r.facets.releaseType)).toEqual({ remux: 1, webdl: 1 });
expect(ids(run({ site: ["b"], releaseType: ["disc", "webdl"] }))).toEqual(["2", "5"]);
expect(ids(run({ releaseGroup: ["flux"] }))).toEqual(["2", "3"]);
expect(ids(run({ hdr: ["SDR"] }))).toEqual(["3", "4", "5"]);
expect(ids(run({ promotion: ["2x"] }))).toEqual(["4"]);
});
it("H&R 三态分面:缺省按未知;只列有资源的档", () => {
const hrItems = [mk("y", { hr: true }), mk("n", { hr: false }), mk("n2", { hr: false }), mk("u")];
const r = applyFacets(hrItems, (x) => x, {}, { now: NOW });
expect(r.facets.hr).toEqual([
{ value: "yes", label: "有", count: 1 },
{ value: "no", label: "无", count: 2 },
{ value: "unknown", label: "未知", count: 1 },
]);
expect(ids(applyFacets(hrItems, (x) => x, { hr: ["no", "unknown"] }, { now: NOW }))).toEqual(["n", "n2", "u"]);
const known = applyFacets([mk("y", { hr: true })], (x) => x, {}, { now: NOW });
expect(known.facets.hr.map((o) => o.value)).toEqual(["yes"]);
});
it("已选但没有结果的值仍列出(计数 0)", () => {
const r = run({ releaseGroup: ["NoSuch"], site: ["zzz"] });
expect(r.items).toEqual([]);
expect(r.facets.releaseGroup.find((o) => o.value === "NoSuch")).toEqual({
value: "NoSuch",
label: "NoSuch",
count: 0,
});
expect(r.facets.site.find((o) => o.value === "zzz")?.count).toBe(0);
});
it("制作组只列前 20(已选的补在后面)", () => {
const many = Array.from({ length: 25 }, (_, i) => mk(String(i), { releaseGroup: `G${i}` }));
const r = applyFacets(many, (x) => x, { releaseGroup: ["g9"] }, { now: NOW });
expect(r.facets.releaseGroup.length).toBe(21);
expect(r.facets.releaseGroup.at(-1)?.value).toBe("G9");
});
it("季 / 集:多季合集展开,整季资源记为整季", () => {
const tv = [
mk("s1", { season: 1, episodes: null }),
mk("s1e2", { season: 1, episodes: { start: 2, end: 3 } }),
mk("s13", { season: 1, seasonEnd: 3, episodes: null }),
mk("movie"),
];
const r = applyFacets(tv, (x) => x, { season: ["2"] }, { now: NOW });
expect(ids(r)).toEqual(["s13"]);
expect(r.facets.season).toEqual([
{ value: "1", label: "S01", count: 3 },
{ value: "2", label: "S02", count: 1 },
{ value: "3", label: "S03", count: 1 },
]);
// 在 S02 下单集没有结果,不列
expect(r.facets.episode).toEqual([{ value: "pack", label: "整季", count: 1 }]);
expect(ids(applyFacets(tv, (x) => x, { season: ["1"], episode: ["3"] }, { now: NOW }))).toEqual(["s1e2"]);
});
it("季集(MP season_episode):一个维度,整季在前、单集在后,季号新的在前", () => {
const tv = [
mk("s1", { season: 1, episodes: null }),
mk("s1b", { season: 1, episodes: null }),
mk("s2", { season: 2, episodes: null }),
mk("s1-3", { season: 1, seasonEnd: 3, episodes: null }),
mk("s1e25", { season: 1, episodes: { start: 25, end: 48 } }),
mk("s2e14", { season: 2, episodes: { start: 14, end: 14 } }),
mk("s2e1", { season: 2, episodes: { start: 1, end: 1 } }),
mk("s2e14-15", { season: 2, episodes: { start: 14, end: 15 } }),
mk("movie"),
];
const r = applyFacets(tv, (x) => x, {}, { now: NOW });
expect(r.facets.seasonEpisode.map((o) => [o.value, o.count])).toEqual([
["S02", 1],
["S01", 2],
["S01-S03", 1],
["S02 E14-E15", 1],
["S02 E14", 1],
["S02 E01", 1],
["S01 E25-E48", 1],
]);
expect(r.facets.seasonEpisode[0]?.label).toBe("S02");
expect(ids(applyFacets(tv, (x) => x, { seasonEpisode: ["S01", "S02 E14"] }, { now: NOW }))).toEqual([
"s1",
"s1b",
"s2e14",
]);
// 计数按其它维度已选条件;在其它条件下为 0 的不列
const free = applyFacets(
tv.map((t) => (t.id === "s2" ? { ...t, promotion: { ...none, type: "free", downloadFactor: 0 } } : t)),
(x) => x,
{ promotion: ["free"] },
{ now: NOW },
);
expect(free.facets.seasonEpisode).toEqual([{ value: "S02", label: "S02", count: 1 }]);
expect(seasonEpisodeValue({ season: null, episodes: { start: 1, end: 2 } })).toBeNull();
expect(["S01 E02", "bogus", "S10", "S02 E10-E12"].sort(compareSeasonEpisode)).toEqual([
"S10",
"S02 E10-E12",
"S01 E02",
"bogus",
]);
});
it("促销与版本类型档位", () => {
expect(promotionBuckets({ type: "2x50%", downloadFactor: 0.5, uploadFactor: 2, expiresAt: null }, NOW)).toEqual([
"2x",
"50%",
]);
expect(promotionBuckets({ type: "30%", downloadFactor: 0.3, uploadFactor: 1, expiresAt: null }, NOW)).toEqual([
"other",
]);
expect(
promotionBuckets({ type: "unknown", downloadFactor: null, uploadFactor: null, expiresAt: null }, NOW),
).toEqual(["none"]);
// 促销已过期 → 无
expect(
promotionBuckets({ type: "free", downloadFactor: 0, uploadFactor: 1, expiresAt: "2026-09-28T00:00:00Z" }, NOW),
).toEqual(["none"]);
expect(["BluRay-Disc", "REMUX", "BluRay", "WEB-DL", "HDTV", "WEBRip", null].map(releaseTypeBucket)).toEqual([
"disc",
"remux",
"encode",
"webdl",
"hdtv",
"other",
"other",
]);
});
it("qf 参数编解码", () => {
const sel = { resolution: ["2160p", "1080p"], promotion: ["50%"], releaseGroup: ["A;B,C:D"] };
const s = formatFacetParam(sel);
expect(s).toBe("resolution:2160p,1080p;promotion:50%25;releaseGroup:A%3BB%2CC%3AD");
expect(parseFacetParam(s)).toEqual(sel);
expect(parseFacetParam("bogus:1;hdr:;hdr:DV,DV")).toEqual({ hdr: ["DV"] });
});
// ── SEARCH-4:对齐 MP 后新增的维度 ──
const GB = 1024 ** 3;
const rich = [
mk("a", {
audio: ["TrueHD", "Atmos 7.1"],
subtitles: ["zh-Hans", "en"],
audioLangs: ["cmn"],
bitDepth: 10,
year: 2024,
category: "电影",
sizeBytes: 60 * GB,
seeders: 80,
}),
mk("b", {
audio: ["DDP 5.1"],
subtitles: ["zh-Hant"],
audioLangs: ["cmn", "yue"],
fps: 60,
year: 2023,
category: "电影",
sizeBytes: 8 * GB,
seeders: 0,
}),
mk("c", { audio: ["DTS-HD MA 5.1", "2Audios"], year: 2024, category: "剧集", sizeBytes: 3 * GB, seeders: 12 }),
mk("d", { audio: [], cut: "Extended", sizeBytes: null, seeders: null }),
];
const run2 = (sel: Parameters<typeof applyFacets>[2]) => applyFacets(rich, (x) => x, sel, { now: NOW });
it("新维度档位与计数:音频 / 字幕 / 配音 / 特性 / 年份 / 分类 / 大小 / 做种", () => {
const r = run2({});
expect(counts(r.facets.audioCodec)).toEqual({
Atmos: 1,
TrueHD: 1,
"DTS-HD": 1,
DDP: 1,
unknown: 1,
});
expect(r.facets.audioCodec.find((o) => o.value === "DDP")?.label).toBe("DD+");
expect(counts(r.facets.subtitle)).toEqual({ zh: 2, "zh-Hans": 1, "zh-Hant": 1, en: 1, unknown: 2 });
expect(counts(r.facets.dub)).toEqual({ cmn: 2, yue: 1, unknown: 2 });
expect(counts(r.facets.feature)).toEqual({ "10bit": 1, hfr: 1, cut: 1 });
// 年份新的在前;分类按条数
expect(r.facets.year.map((o) => [o.value, o.count])).toEqual([
["2024", 2],
["2023", 1],
]);
expect(r.facets.category.map((o) => [o.label, o.count])).toEqual([
["电影", 2],
["剧集", 1],
]);
expect(counts(r.facets.size)).toEqual({ "1-5": 1, "5-10": 1, "50+": 1, unknown: 1 });
expect(counts(r.facets.seeders)).toEqual({ "0": 1, "10-49": 1, "50+": 1, unknown: 1 });
});
it("新维度组合:同维度 OR、跨维度 AND,计数按其它维度已选条件", () => {
// 中文字幕 AND 国语 → a、b;再加 Atmos → 只剩 a
expect(ids(run2({ subtitle: ["zh"], dub: ["cmn"] }))).toEqual(["a", "b"]);
const r = run2({ subtitle: ["zh"], dub: ["cmn"], audioCodec: ["Atmos"] });
expect(ids(r)).toEqual(["a"]);
// 音频维度自己的计数不受已选 Atmos 影响:DD+ 仍可多出 b
expect(counts(r.facets.audioCodec).DDP).toBe(1);
// 同维度 OR:Atmos 或 DTS-HD
expect(ids(run2({ audioCodec: ["Atmos", "DTS-HD"] }))).toEqual(["a", "c"]);
// 年份 × 分类 × 做种
expect(ids(run2({ year: ["2024"], category: ["电影", "剧集"], seeders: ["10-49", "50+"] }))).toEqual(["a", "c"]);
expect(ids(run2({ size: ["unknown"], feature: ["cut"] }))).toEqual(["d"]);
// 已选但当前没有资源的年份列出、计数 0
expect(run2({ year: ["1999"] }).facets.year.find((o) => o.value === "1999")).toEqual({
value: "1999",
label: "1999",
count: 0,
});
});
it("新维度的归档函数", () => {
expect(audioBuckets(["E-AC3", "AC3 2.0", "PCM", "Opus", "5.1"])).toEqual(["DDP", "DD", "LPCM", "other"]);
expect(audioBuckets(["2Audios"])).toEqual(["unknown"]);
expect(subtitleBuckets(["zh"])).toEqual(["zh"]);
expect(subtitleBuckets(null)).toEqual(["unknown"]);
expect([0.5 * GB, GB, 49.9 * GB, 50 * GB, null, -1].map(sizeBucket)).toEqual([
"lt1",
"1-5",
"20-50",
"50+",
"unknown",
"unknown",
]);
expect([0, 9, 10, 50, null].map(seedersBucket)).toEqual(["0", "1-9", "10-49", "50+", "unknown"]);
});
it("解析器补齐配音语言(MP 的 CNVOI / HKVOI)", () => {
expect(parseRelease("Movie 2020 1080p 国语中字 WEB-DL").audioLangs).toEqual(["cmn"]);
expect(parseRelease("某剧 第01集 国粤双语 1080p").audioLangs).toEqual(["cmn", "yue"]);
expect(parseRelease("Movie.2020.1080p.BluRay.x264-GRP").audioLangs).toBeUndefined();
expect(
parseRelease("Movie.2020.2160p.BluRay.x265-GRP", { subtitle: "电影 | 国英双语 简繁字幕" }).audioLangs,
).toEqual(["cmn", "en"]);
});
});