Files
UHD_Media_Tool/packages/domain/test/release-facets.test.ts
T
rootandClaude Opus 5.5 d9cf8f1c3a feat(search): align quick-filter facets with MP (SEARCH-4)
Add audioCodec, subtitle, dub, feature, year, category, size and seeders
facets with disjunctive counts; parser records dub languages (audioLangs).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-30 22:40:55 +08:00

306 lines
12 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,
type FacetInput,
formatFacetParam,
parseFacetParam,
parseRelease,
promotionBuckets,
releaseTypeBucket,
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"]);
expect(counts(r.facets.hdr)).toEqual({ DV: 1, HDR10: 2, "HDR10+": 0, SDR: 3 });
expect(counts(r.facets.promotion)).toEqual({ free: 2, "2x": 1, "50%": 0, 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([]);
});
it("同维度 OR、不同维度 AND;计数按其它维度的已选条件", () => {
const r = run({ resolution: ["2160p", "1080p"], videoCodec: ["H.265"] });
expect(ids(r)).toEqual(["1", "2"]);
// 分辨率的计数只受编码条件影响:1080p 在 H.265 下没有结果 → 0
expect(counts(r.facets.resolution)).toEqual({ "2160p": 2, "1080p": 0, "720p": 0, other: 0 });
// 编码计数只受分辨率条件影响
expect(counts(r.facets.videoCodec)).toEqual({ "H.265": 2, "H.264": 1, AV1: 0, other: 0 });
// 其它维度按全部条件
expect(counts(r.facets.releaseType)).toMatchObject({ remux: 1, webdl: 1, encode: 0 });
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", "no"]);
});
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 },
]);
expect(r.facets.episode).toEqual([
{ value: "pack", label: "整季", count: 1 },
{ value: "2", label: "E02", count: 0 },
{ value: "3", label: "E03", count: 0 },
]);
expect(ids(applyFacets(tv, (x) => x, { season: ["1"], episode: ["3"] }, { now: NOW }))).toEqual(["s1e2"]);
});
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,
DTS: 0,
DDP: 1,
DD: 0,
AAC: 0,
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({
lt1: 0,
"1-5": 1,
"5-10": 1,
"10-20": 0,
"20-50": 0,
"50+": 1,
unknown: 1,
});
expect(counts(r.facets.seeders)).toEqual({ "0": 1, "1-9": 0, "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"]);
});
});