Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
192 lines
8.8 KiB
TypeScript
192 lines
8.8 KiB
TypeScript
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import { type DiscoverItem, DiscoverItem as DiscoverItemSchema } from "@umt/contracts";
|
|
import { createElement } from "react";
|
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
import { MemoryRouter } from "react-router";
|
|
import { describe, expect, it } from "vitest";
|
|
import { DiscoverCard } from "./discover-card.tsx";
|
|
import {
|
|
apiQuery,
|
|
type ExploreState,
|
|
fromSearchParams,
|
|
itemHref,
|
|
itemMeta,
|
|
localBadges,
|
|
normalize,
|
|
patch,
|
|
RECOMMEND_SECTIONS,
|
|
sectionsOf,
|
|
tmdbListOptions,
|
|
toSearchParams,
|
|
} from "./discover-state.ts";
|
|
import { ExploreFilters } from "./explore-view.tsx";
|
|
import { RecommendView } from "./recommend-view.tsx";
|
|
|
|
// 发现页:URL ↔ 状态、筛选器联动、接口参数、卡片状态与去处、分区与筛选器渲染。
|
|
|
|
const item = (p: Partial<DiscoverItem> = {}): DiscoverItem =>
|
|
DiscoverItemSchema.parse({
|
|
source: "tmdb",
|
|
id: "movie:872585",
|
|
title: "奥本海默",
|
|
aliases: [],
|
|
year: 2023,
|
|
kind: "movie",
|
|
cover: null,
|
|
rating: 8.1,
|
|
date: "2023-07-19",
|
|
episodes: null,
|
|
url: "https://www.themoviedb.org/movie/872585",
|
|
genres: ["剧情", "历史", "传记"],
|
|
posterPath: "/oppenheimer.jpg",
|
|
tmdb: { type: "movie", id: "872585" },
|
|
...p,
|
|
});
|
|
|
|
const render = (el: ReturnType<typeof createElement>) =>
|
|
renderToStaticMarkup(
|
|
createElement(QueryClientProvider, { client: new QueryClient() }, createElement(MemoryRouter, null, el)),
|
|
);
|
|
|
|
describe("探索状态规整与联动", () => {
|
|
it("缺省:TMDB 本周流行趋势;URL 往返", () => {
|
|
const s = fromSearchParams(new URLSearchParams());
|
|
expect(s).toEqual({ src: "tmdb", list: "trending", type: "all", window: "week" });
|
|
const t = normalize({ src: "tmdb", list: "discover", type: "tv", genre: "10765", year: "2010s", sort: "rating" });
|
|
expect(fromSearchParams(toSearchParams(t))).toEqual(t);
|
|
expect(toSearchParams(t).get("view")).toBe("explore");
|
|
});
|
|
|
|
it("类型与列表互相约束:正在上映只有电影,全部只用于趋势 / 搜索", () => {
|
|
expect(normalize({ src: "tmdb", list: "now_playing", type: "tv" }).type).toBe("movie");
|
|
expect(normalize({ src: "tmdb", list: "popular", type: "all" }).type).toBe("movie");
|
|
expect(tmdbListOptions("tv").map((o) => o.value)).not.toContain("now_playing");
|
|
expect(tmdbListOptions("movie").map((o) => o.value)).not.toContain("on_the_air");
|
|
});
|
|
|
|
it("改类型去掉不配的风格;离开筛选清掉条件;芯片再点一次取消", () => {
|
|
const s = normalize({ src: "tmdb", list: "discover", type: "movie", genre: "28", region: "US" });
|
|
expect(patch(s, { type: "tv" })).toMatchObject({ type: "tv", region: "US" });
|
|
expect(patch(s, { type: "tv" }).genre).toBeUndefined(); // 28 动作 只有电影有
|
|
expect(patch(s, { list: "popular" })).toEqual({ src: "tmdb", list: "popular", type: "movie" });
|
|
expect(patch(s, { region: "US" }).region).toBeUndefined();
|
|
expect(patch(s, { region: "JP" }).region).toBe("JP");
|
|
});
|
|
|
|
it("换来源回到该来源默认列表;关键词切到搜索,清空回到默认", () => {
|
|
const s = normalize({ src: "tmdb", list: "discover", type: "movie", genre: "28" });
|
|
expect(patch(s, { src: "douban" })).toEqual({ src: "douban", list: "movie_real_time_hotest", type: "movie" });
|
|
const q = patch(s, { q: " 沙丘 " });
|
|
expect(q).toMatchObject({ list: "search", q: "沙丘", type: "movie" });
|
|
expect(q.genre).toBeUndefined();
|
|
expect(patch(q, { q: "" }).list).toBe("trending");
|
|
});
|
|
|
|
it("豆瓣标签按类型校正;bgm 每日放送补星期,年份只给排行 / 最新", () => {
|
|
expect(normalize({ src: "douban", list: "tag", type: "tv", tag: "豆瓣高分" }).tag).toBe("热门");
|
|
expect(normalize({ src: "douban", list: "tag", type: "tv", tag: "美剧" }).tag).toBe("美剧");
|
|
expect(normalize({ src: "douban", list: "nope" }).list).toBe("movie_real_time_hotest");
|
|
expect(normalize({ src: "bangumi", list: "calendar", weekday: "3" }).weekday).toBe("3");
|
|
expect(normalize({ src: "bangumi", list: "calendar" }).weekday).toMatch(/^[1-7]$/);
|
|
expect(normalize({ src: "bangumi", list: "trends", year: "2024" }).year).toBeUndefined();
|
|
expect(normalize({ src: "bangumi", list: "rank", year: "2024" }).year).toBe("2024");
|
|
});
|
|
|
|
it("接口参数:只带当前列表用得到的", () => {
|
|
expect(apiQuery(normalize({}))).toEqual({
|
|
kind: "list",
|
|
path: "/discover/tmdb",
|
|
params: { list: "trending", type: "all", window: "week" },
|
|
});
|
|
expect(apiQuery(normalize({ src: "tmdb", list: "discover", type: "movie", minRating: "7" }))).toEqual({
|
|
kind: "list",
|
|
path: "/discover/tmdb",
|
|
params: { list: "discover", type: "movie", minRating: "7" },
|
|
});
|
|
expect(apiQuery(normalize({ src: "douban", list: "explore", type: "tv", genre: "悬疑", sort: "S" }))).toEqual({
|
|
kind: "list",
|
|
path: "/discover/douban",
|
|
params: { list: "explore", type: "tv", sort: "S", genre: "悬疑" },
|
|
});
|
|
expect(apiQuery(normalize({ src: "bangumi", list: "calendar", weekday: "5" }))).toEqual({
|
|
kind: "calendar",
|
|
weekday: "5",
|
|
});
|
|
expect(apiQuery(normalize({ src: "bangumi", q: "芙莉莲" }))).toEqual({
|
|
kind: "list",
|
|
path: "/discover/bangumi",
|
|
params: { list: "search", q: "芙莉莲" },
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("卡片", () => {
|
|
it("去处:本地作品 → 媒体库;TMDB → TMDB 作品页;没匹配过的豆瓣条目 → 先匹配", () => {
|
|
const local = { entityId: "e1", subscribed: true, versionCount: 0, downloadingCount: 0 };
|
|
expect(itemHref(item({ local }))).toBe("/console/library/e1");
|
|
expect(itemHref(item())).toBe("/console/media/tmdb/movie/872585");
|
|
expect(itemHref(item({ source: "douban", id: "1", tmdb: null }))).toBeNull();
|
|
});
|
|
|
|
it("本地状态只列非默认:已有 / 下载中 / 已订阅;在媒体库但都没有时写在媒体库", () => {
|
|
const l = (p: object) => ({ entityId: "e", subscribed: false, versionCount: 0, downloadingCount: 0, ...p });
|
|
expect(localBadges(item())).toEqual([]);
|
|
expect(localBadges(item({ local: l({ versionCount: 2, subscribed: true }) })).map((b) => b.label)).toEqual([
|
|
"媒体库已有",
|
|
"已订阅",
|
|
]);
|
|
expect(localBadges(item({ local: l({ downloadingCount: 1 }) })).map((b) => b.label)).toEqual(["下载中"]);
|
|
expect(localBadges(item({ local: l({}) })).map((b) => b.label)).toEqual(["在媒体库"]);
|
|
expect(itemMeta(item())).toBe("2023 · 电影 · 剧情 · 历史");
|
|
});
|
|
|
|
it("渲染:海报懒加载、评分、状态徽章、订阅与搜索资源", () => {
|
|
const html = render(
|
|
createElement(DiscoverCard, {
|
|
item: item({ local: { entityId: "e", subscribed: true, versionCount: 1, downloadingCount: 0 } }),
|
|
busy: null,
|
|
onAction: () => {},
|
|
}),
|
|
);
|
|
expect(html).toContain('loading="lazy"');
|
|
expect(html).toContain("/api/v1/img/tmdb/w342/oppenheimer.jpg");
|
|
expect(html).toContain("8.1");
|
|
expect(html).toContain("媒体库已有");
|
|
expect(html).toContain("已订阅");
|
|
expect(html).toContain("订阅");
|
|
expect(html).toContain("搜索资源");
|
|
});
|
|
});
|
|
|
|
describe("分区与筛选器", () => {
|
|
it("推荐分区覆盖 TMDB 趋势 / 热门、豆瓣、Bangumi;按分类筛", () => {
|
|
const keys = RECOMMEND_SECTIONS.map((s) => s.key);
|
|
expect(keys).toEqual(
|
|
expect.arrayContaining(["tmdb-trending", "tmdb-popular-tv", "douban-movie-hot", "bangumi-today"]),
|
|
);
|
|
expect(sectionsOf("anime").every((s) => s.category === "anime")).toBe(true);
|
|
// 每个分区的参数都是规整后的合法状态(查看全部能直接打开)
|
|
for (const s of RECOMMEND_SECTIONS) expect(normalize(normalize(s.explore))).toEqual(normalize(s.explore));
|
|
const html = render(createElement(RecommendView, { category: "tv", onCategory: () => {} }));
|
|
for (const s of sectionsOf("tv")) expect(html).toContain(s.title);
|
|
expect(html).not.toContain("TMDB 流行趋势");
|
|
expect(html).toContain("查看全部");
|
|
expect(html).toContain('aria-hidden="true"'); // 骨架屏
|
|
});
|
|
|
|
it("筛选器随来源 / 列表出现", () => {
|
|
const html = (s: ExploreState) => render(createElement(ExploreFilters, { s, onPatch: () => {} }));
|
|
const disc = html(normalize({ src: "tmdb", list: "discover", type: "movie" }));
|
|
for (const l of ["风格", "地区", "语言", "年份", "评分", "排序", "科幻"]) expect(disc).toContain(l);
|
|
const trending = html(normalize({ src: "tmdb" }));
|
|
expect(trending).toContain("本周");
|
|
expect(trending).not.toContain("风格");
|
|
const douban = html(normalize({ src: "douban", list: "explore" }));
|
|
expect(douban).toContain("年代");
|
|
expect(douban).toContain("悬疑");
|
|
const bgm = html(normalize({ src: "bangumi", list: "calendar", weekday: "1" }));
|
|
expect(bgm).toContain("周一");
|
|
expect(bgm).toContain("在 Bangumi 搜动画");
|
|
});
|
|
});
|