Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
209 lines
7.4 KiB
TypeScript
209 lines
7.4 KiB
TypeScript
import type { CoverageCell, Subscription, SubscriptionSlotSummary } from "@umt/contracts";
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
cellKind,
|
|
draftFromSubscription,
|
|
draftToParams,
|
|
emptyDraft,
|
|
entriesByDate,
|
|
foldNames,
|
|
monthGrid,
|
|
monthKey,
|
|
monthStart,
|
|
moveItem,
|
|
progressText,
|
|
seasonLabel,
|
|
shiftMonth,
|
|
slotBadge,
|
|
upgradeOf,
|
|
} from "./model.ts";
|
|
|
|
const slot = (x: Partial<SubscriptionSlotSummary>): SubscriptionSlotSummary => ({
|
|
slotId: "s",
|
|
name: "4K",
|
|
primary: true,
|
|
status: "empty",
|
|
have: 0,
|
|
inflight: 0,
|
|
missing: 0,
|
|
total: null,
|
|
upgrading: false,
|
|
minPri: null,
|
|
targetPri: null,
|
|
autoFill: true,
|
|
ruleGroupIds: [],
|
|
...x,
|
|
});
|
|
const cell = (x: Partial<CoverageCell>): CoverageCell => ({
|
|
unitKey: "s1e1",
|
|
state: "missing",
|
|
assetId: null,
|
|
quality: null,
|
|
pri: null,
|
|
planId: null,
|
|
planStage: null,
|
|
reason: null,
|
|
upgradable: false,
|
|
deferred: false,
|
|
...x,
|
|
});
|
|
|
|
describe("订阅总览文案", () => {
|
|
it("进度总未知显示 ?", () => {
|
|
expect(progressText({ have: 3, total: null, inflight: 0 })).toBe("3/?");
|
|
expect(progressText({ have: 3, total: 10, inflight: 1 })).toBe("3/10");
|
|
});
|
|
it("季标签:特别篇 / 接续 / 电影", () => {
|
|
expect(seasonLabel({ mediaKind: "series", seasonNumber: 0, seasonScope: "current_season" })).toBe("特别篇");
|
|
expect(seasonLabel({ mediaKind: "series", seasonNumber: 2, seasonScope: "following_seasons" })).toBe("S2 起");
|
|
expect(seasonLabel({ mediaKind: "series", seasonNumber: 1, seasonScope: "current_season" })).toBe("S1");
|
|
expect(seasonLabel({ mediaKind: "movie", seasonNumber: null, seasonScope: "current_season" })).toBeNull();
|
|
});
|
|
it("版本徽标按 洗版 → 下载中 → 缺 → 已齐 判定", () => {
|
|
expect(slotBadge(slot({ upgrading: true, minPri: 97, targetPri: 100, missing: 2 })).label).toBe("洗版中 97→100");
|
|
expect(slotBadge(slot({ inflight: 2, missing: 3 }))).toEqual({ label: "下载中 2", tone: "neutral" });
|
|
expect(slotBadge(slot({ missing: 3 })).tone).toBe("destructive");
|
|
expect(slotBadge(slot({ have: 10, total: 10 }))).toEqual({ label: "已齐", tone: "success" });
|
|
expect(slotBadge(slot({})).label).toBe("未开始");
|
|
});
|
|
it("名称折叠", () => {
|
|
expect(foldNames([])).toBe("全部站点");
|
|
expect(foldNames(["a", "b", "c", "d"])).toBe("a、b +2");
|
|
});
|
|
});
|
|
|
|
describe("覆盖矩阵格", () => {
|
|
it("state + 附带字段 → 显示类别", () => {
|
|
expect(cellKind(cell({ state: "have" }))).toBe("have");
|
|
expect(cellKind(cell({ state: "have", upgradable: true }))).toBe("upgradable");
|
|
expect(cellKind(cell({ state: "missing", reason: "suspected" }))).toBe("suspected");
|
|
expect(cellKind(cell({ state: "missing", deferred: true }))).toBe("deferred");
|
|
expect(cellKind(cell({ state: "skipped", reason: "not_aired" }))).toBe("not_aired");
|
|
expect(cellKind(cell({ state: "skipped", reason: "out_of_range" }))).toBe("skipped");
|
|
expect(cellKind(cell({ state: "inflight" }))).toBe("inflight");
|
|
});
|
|
it("拖动排序", () => {
|
|
expect(moveItem(["a", "b", "c"], 2, 0)).toEqual(["c", "a", "b"]);
|
|
expect(moveItem(["a", "b"], 0, 5)).toEqual(["a", "b"]);
|
|
});
|
|
it("升级策略取自收录版本", () => {
|
|
expect(upgradeOf([{ upgradeThreshold: {} }], "series")).toBe("off");
|
|
expect(upgradeOf([{ upgradeThreshold: { enabled: true, mode: "full_pack" } }], "series")).toBe("full_pack");
|
|
expect(upgradeOf([{ upgradeThreshold: { enabled: true } }], "movie")).toBe("movie");
|
|
});
|
|
});
|
|
|
|
describe("表单 ↔ params", () => {
|
|
it("保留未知键,空值不写,只在改动时带 best_version / total_episode", () => {
|
|
const d = {
|
|
...emptyDraft("series"),
|
|
siteIds: ["x"],
|
|
episodeStart: "3",
|
|
include: "中字\n\n 4K ",
|
|
autoFill: "off" as const,
|
|
unitsPerDay: "2",
|
|
gbPerDay: "1.5",
|
|
total: "12",
|
|
upgrade: "full_pack" as const,
|
|
};
|
|
const { params, errors } = draftToParams(
|
|
d,
|
|
{ custom: 1, keywords: ["旧"] },
|
|
{ upgradeChanged: true, totalChanged: true },
|
|
);
|
|
expect(errors).toEqual({});
|
|
expect(params).toEqual({
|
|
custom: 1,
|
|
siteIds: ["x"],
|
|
include: ["中字", "4K"],
|
|
episodeRange: { start: 3 },
|
|
total_episode: 12,
|
|
manual_total_episode: false,
|
|
autoFill: "off",
|
|
fillBudget: { unitsPerDay: 2, bytesPerDay: Math.round(1.5 * 1024 ** 3) },
|
|
best_version: true,
|
|
best_version_full: true,
|
|
});
|
|
const same = draftToParams(d, {}, { upgradeChanged: false, totalChanged: false }).params;
|
|
expect(same.best_version).toBeUndefined();
|
|
expect(same.total_episode).toBeUndefined();
|
|
});
|
|
it("校验:结束集 < 开始集", () => {
|
|
const { errors } = draftToParams(
|
|
{ ...emptyDraft("series"), episodeStart: "5", episodeEnd: "2" },
|
|
{},
|
|
{ upgradeChanged: false, totalChanged: false },
|
|
);
|
|
expect(errors.episodeEnd).toBeTruthy();
|
|
});
|
|
it("从订阅回填", () => {
|
|
const sub = {
|
|
mediaKind: "series",
|
|
seasonUnitId: null,
|
|
seasonScope: "current_season",
|
|
mode: "continuous",
|
|
priority: "tracking",
|
|
policyRevisionId: null,
|
|
params: {
|
|
episodeRange: { start: 2 },
|
|
autoFill: "on",
|
|
fillBudget: { bytesPerDay: 2 * 1024 ** 3 },
|
|
keywords: ["a", "b"],
|
|
},
|
|
} as unknown as Subscription;
|
|
const d = draftFromSubscription(sub, "per_unit");
|
|
expect(d.episodeStart).toBe("2");
|
|
expect(d.autoFill).toBe("on");
|
|
expect(d.gbPerDay).toBe("2");
|
|
expect(d.keywords).toBe("a\nb");
|
|
expect(d.upgrade).toBe("per_unit");
|
|
});
|
|
it("质量预设:草稿 ↔ params;未知值回填为空", () => {
|
|
const d = { ...emptyDraft("movie"), resolution: "4k" as const, source: "remux" as const, effect: "dv" as const };
|
|
const { params } = draftToParams(
|
|
d,
|
|
{ resolution: "720p", effect: "sdr" },
|
|
{ upgradeChanged: false, totalChanged: false },
|
|
);
|
|
expect(params).toEqual({ resolution: "4k", source: "remux", effect: "dv" });
|
|
const cleared = draftToParams(
|
|
emptyDraft("movie"),
|
|
{ resolution: "720p" },
|
|
{ upgradeChanged: false, totalChanged: false },
|
|
);
|
|
expect(cleared.params.resolution).toBeUndefined();
|
|
const back = draftFromSubscription(
|
|
{
|
|
mediaKind: "movie",
|
|
seasonScope: "current_season",
|
|
mode: "once",
|
|
priority: "tracking",
|
|
seasonUnitId: null,
|
|
policyRevisionId: null,
|
|
params: { resolution: "1080p", source: "bogus", effect: "hdr" },
|
|
} as unknown as Subscription,
|
|
"off",
|
|
);
|
|
expect([back.resolution, back.source, back.effect]).toEqual(["1080p", "", "hdr"]);
|
|
});
|
|
});
|
|
|
|
describe("日历", () => {
|
|
it("月网格从周一开始,整周铺满;跨年切月", () => {
|
|
const first = monthStart("2026-09");
|
|
const g = monthGrid(first);
|
|
expect(g[0]).toEqual({ date: "2026-08-31", inMonth: false });
|
|
expect(g.length % 7).toBe(0);
|
|
expect(g.at(-1)).toEqual({ date: "2026-10-04", inMonth: false });
|
|
expect(g.filter((d) => d.inMonth)).toHaveLength(30);
|
|
expect(monthKey(shiftMonth(monthStart("2026-12"), 1))).toBe("2027-01");
|
|
expect(monthKey(monthStart("bad", new Date(2026, 4, 9)))).toBe("2026-05");
|
|
});
|
|
it("按日期分桶", () => {
|
|
const e = (date: string) => ({ date, subscriptionId: date }) as never;
|
|
const m = entriesByDate([e("2026-09-01"), e("2026-09-01"), e("2026-09-02")]);
|
|
expect(m.get("2026-09-01")).toHaveLength(2);
|
|
expect(m.get("2026-09-03")).toBeUndefined();
|
|
});
|
|
});
|